tradeguard-oss 0.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. tradeguard_oss-0.7.0/LICENSE +21 -0
  2. tradeguard_oss-0.7.0/PKG-INFO +177 -0
  3. tradeguard_oss-0.7.0/README.md +163 -0
  4. tradeguard_oss-0.7.0/pyproject.toml +26 -0
  5. tradeguard_oss-0.7.0/setup.cfg +4 -0
  6. tradeguard_oss-0.7.0/src/tradeguard/__init__.py +68 -0
  7. tradeguard_oss-0.7.0/src/tradeguard/analytics.py +158 -0
  8. tradeguard_oss-0.7.0/src/tradeguard/cli.py +176 -0
  9. tradeguard_oss-0.7.0/src/tradeguard/diagnostics.py +59 -0
  10. tradeguard_oss-0.7.0/src/tradeguard/importers.py +122 -0
  11. tradeguard_oss-0.7.0/src/tradeguard/integrity.py +53 -0
  12. tradeguard_oss-0.7.0/src/tradeguard/io.py +43 -0
  13. tradeguard_oss-0.7.0/src/tradeguard/models.py +41 -0
  14. tradeguard_oss-0.7.0/src/tradeguard/risk.py +243 -0
  15. tradeguard_oss-0.7.0/src/tradeguard/schema.py +11 -0
  16. tradeguard_oss-0.7.0/src/tradeguard/validation.py +37 -0
  17. tradeguard_oss-0.7.0/src/tradeguard_oss.egg-info/PKG-INFO +177 -0
  18. tradeguard_oss-0.7.0/src/tradeguard_oss.egg-info/SOURCES.txt +30 -0
  19. tradeguard_oss-0.7.0/src/tradeguard_oss.egg-info/dependency_links.txt +1 -0
  20. tradeguard_oss-0.7.0/src/tradeguard_oss.egg-info/entry_points.txt +2 -0
  21. tradeguard_oss-0.7.0/src/tradeguard_oss.egg-info/requires.txt +4 -0
  22. tradeguard_oss-0.7.0/src/tradeguard_oss.egg-info/top_level.txt +1 -0
  23. tradeguard_oss-0.7.0/tests/test_cli.py +180 -0
  24. tradeguard_oss-0.7.0/tests/test_core.py +68 -0
  25. tradeguard_oss-0.7.0/tests/test_diagnostics.py +26 -0
  26. tradeguard_oss-0.7.0/tests/test_importers.py +94 -0
  27. tradeguard_oss-0.7.0/tests/test_integrity.py +36 -0
  28. tradeguard_oss-0.7.0/tests/test_io.py +36 -0
  29. tradeguard_oss-0.7.0/tests/test_report_contract.py +84 -0
  30. tradeguard_oss-0.7.0/tests/test_risk.py +162 -0
  31. tradeguard_oss-0.7.0/tests/test_segmentation.py +47 -0
  32. tradeguard_oss-0.7.0/tests/test_temporal_analytics.py +68 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 hesam
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,177 @@
1
+ Metadata-Version: 2.4
2
+ Name: tradeguard-oss
3
+ Version: 0.7.0
4
+ Summary: Open-source trade journal validation, risk analytics, and data-quality toolkit for traders and trading systems.
5
+ Author: TradeGuard OSS contributors
6
+ License: MIT
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Provides-Extra: dev
11
+ Requires-Dist: pytest>=8.0; extra == "dev"
12
+ Requires-Dist: build>=1.2; extra == "dev"
13
+ Dynamic: license-file
14
+
15
+ # TradeGuard OSS
16
+
17
+ [![CI](https://github.com/hesam1111111111/tradeguard-oss/actions/workflows/ci.yml/badge.svg)](https://github.com/hesam1111111111/tradeguard-oss/actions/workflows/ci.yml)
18
+ [![Latest release](https://img.shields.io/github/v/release/hesam1111111111/tradeguard-oss)](https://github.com/hesam1111111111/tradeguard-oss/releases/latest)
19
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
20
+ [![Python 3.10–3.13](https://img.shields.io/badge/Python-3.10%E2%80%933.13-blue.svg)](.github/workflows/ci.yml)
21
+
22
+ TradeGuard OSS is an open-source toolkit for validating trading journals, checking risk hygiene, and computing reproducible performance and journal-integrity diagnostics from closed trades.
23
+
24
+ > Status: active early development (`v0.7.0`). The project is intended for research, education, journaling, and system-quality checks. It is not financial advice and it does not place trades.
25
+
26
+ ## Why TradeGuard?
27
+
28
+ Trading journals often contain missing stop losses, inconsistent direction labels, invalid timestamps, duplicate records, incomplete position sizing, or performance statistics that cannot be reproduced. TradeGuard turns those checks into dependency-light, testable rules and deterministic reports.
29
+
30
+ ## Current capabilities
31
+
32
+ - Versioned CSV journal schema and row-level diagnostics
33
+ - Long/short PnL, initial risk, and R-multiple calculation
34
+ - Win rate, net PnL, expectancy, gross profit/loss, profit factor, breakeven count, best/worst trade, and closed-trade maximum drawdown
35
+ - Stop-loss and data-quality validation
36
+ - Deterministic SHA-256 journal fingerprints
37
+ - Exact duplicate-trade detection and blocking integrity diagnostics
38
+ - Entry-notional portfolio exposure by normalized symbol and side
39
+ - Gross/net notional exposure and configurable portfolio, symbol, and trade notional limits
40
+ - Stop-based historical risk budgets with explicit incomplete-data diagnostics
41
+ - Deterministic segmented analytics by symbol and side
42
+ - Optional deterministic closed-at grouping by calendar day or month
43
+ - Explicit mapped CSV imports with source-row provenance and rejection diagnostics
44
+ - Stable additive `tradeguard.report.v1` contract with explicit compatibility rules
45
+ - Human-readable or versioned JSON CLI output
46
+ - Deterministic JSON report export
47
+ - Automated tests across Python 3.10–3.13 plus distribution wheel smoke-install validation
48
+
49
+ ## Install for development
50
+
51
+ ```bash
52
+ git clone https://github.com/hesam1111111111/tradeguard-oss.git
53
+ cd tradeguard-oss
54
+ python -m venv .venv
55
+ # Windows: .venv\Scripts\activate
56
+ # macOS/Linux: source .venv/bin/activate
57
+ pip install -e .[dev]
58
+ pytest -q
59
+ ```
60
+
61
+ ## CSV format
62
+
63
+ Required columns:
64
+
65
+ ```text
66
+ symbol,side,entry,exit
67
+ ```
68
+
69
+ Optional columns:
70
+
71
+ ```text
72
+ stop_loss,quantity,opened_at,closed_at
73
+ ```
74
+
75
+ Example:
76
+
77
+ ```csv
78
+ symbol,side,entry,exit,stop_loss,quantity,opened_at,closed_at
79
+ BTCUSDT,long,60000,61500,59000,0.1,2026-01-01T10:00:00,2026-01-01T13:00:00
80
+ ETHUSDT,short,3200,3100,3260,1.0,2026-01-02T09:00:00,2026-01-02T12:00:00
81
+ ```
82
+
83
+ ## CLI
84
+
85
+ Native TradeGuard CSV:
86
+
87
+ ```bash
88
+ tradeguard examples/sample_journal.csv
89
+ tradeguard examples/sample_journal.csv --json
90
+ tradeguard examples/sample_journal.csv --output report.json
91
+ ```
92
+
93
+ Explicit mapped import from a differently named CSV:
94
+
95
+ ```bash
96
+ tradeguard examples/mapped_journal.csv \
97
+ --map symbol=Ticker \
98
+ --map side=Direction \
99
+ --map entry=OpenPrice \
100
+ --map exit=ClosePrice \
101
+ --map stop_loss=Stop \
102
+ --map quantity=Size \
103
+ --json
104
+ ```
105
+
106
+ Mappings are explicit by design. TradeGuard does not guess aliases or infer ambiguous columns. The report adds an `import` provenance section with source/imported/rejected row counts, the exact mapping, completeness, and source-indexed diagnostics. If mapped import is incomplete, metrics/risk/segments are suppressed rather than computed from a partial dataset.
107
+
108
+ Add deterministic temporal analytics based on the recorded `closed_at` value:
109
+
110
+ ```bash
111
+ tradeguard examples/sample_journal.csv --group-closed-by day --json
112
+ tradeguard examples/sample_journal.csv --group-closed-by month --output report.json
113
+ ```
114
+
115
+ The report retains the `tradeguard.report.v1` envelope and includes source, metrics, validation issues, journal fingerprint, structured integrity diagnostics, risk analysis, segmented analytics, and optional import provenance. Metrics are skipped when blocking validation, duplicate-record errors, or incomplete mapped import make analysis unsafe.
116
+
117
+ The stable machine-readable contract and compatibility rules are documented in [`docs/report-contract-v1.md`](docs/report-contract-v1.md).
118
+
119
+ ## Python API
120
+
121
+ ```python
122
+ from tradeguard import (
123
+ RiskLimits,
124
+ Trade,
125
+ aggregate_exposure,
126
+ analyze_by_closed_period,
127
+ analyze_trades,
128
+ check_risk_limits,
129
+ import_mapped_csv,
130
+ journal_fingerprint,
131
+ validate_trades,
132
+ )
133
+
134
+ trades = [Trade("BTCUSDT", "long", entry=60000, exit=61500, stop_loss=59000, quantity=0.1)]
135
+ print(validate_trades(trades))
136
+ print(journal_fingerprint(trades))
137
+ print(analyze_trades(trades))
138
+ print(analyze_by_closed_period(trades, "month"))
139
+ print(aggregate_exposure(trades))
140
+ print(check_risk_limits(trades, RiskLimits(max_gross_notional=10000)))
141
+
142
+ mapped = import_mapped_csv(
143
+ "examples/mapped_journal.csv",
144
+ {"symbol": "Ticker", "side": "Direction", "entry": "OpenPrice", "exit": "ClosePrice"},
145
+ )
146
+ print(mapped.imported_rows, mapped.rejected_rows)
147
+ ```
148
+
149
+ ### Exposure semantics
150
+
151
+ `aggregate_exposure` and notional risk limits use absolute `entry * quantity` values from the supplied journal. They describe historical entry-notional concentration; they are **not** live positions, mark-to-market exposure, margin usage, or broker account state.
152
+
153
+ ### Temporal semantics
154
+
155
+ Temporal grouping uses the recorded `closed_at` value exactly as supplied. TradeGuard does not guess or convert timezones; callers combining timestamps from different zones should normalize them before calendar grouping.
156
+
157
+ ## Development policy
158
+
159
+ Behavioral changes should arrive through scoped branches and pull requests with regression tests. CI runs the test suite across supported Python versions before changes are merged. Public examples must be synthetic or privacy-safe.
160
+
161
+ ## Roadmap
162
+
163
+ Near-term work includes additional offline import adapters, stronger source-data integrity diagnostics, and broader report-consumer fixtures. Live brokerage connectivity and order execution are outside the current core scope.
164
+
165
+ ## Contributing
166
+
167
+ Contributions are welcome. Please read [`CONTRIBUTING.md`](CONTRIBUTING.md), follow the [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md), open an issue for material changes, and include tests for behavioral changes.
168
+
169
+ Repository-maintainer review criteria and evidence are tracked in [`docs/oss-application-readiness.md`](docs/oss-application-readiness.md).
170
+
171
+ ## Security and privacy
172
+
173
+ TradeGuard does not require API keys for its core journal analytics. Do not commit broker credentials, exchange keys, private trade exports, or personal financial data. See [`SECURITY.md`](SECURITY.md).
174
+
175
+ ## License
176
+
177
+ MIT. See [`LICENSE`](LICENSE).
@@ -0,0 +1,163 @@
1
+ # TradeGuard OSS
2
+
3
+ [![CI](https://github.com/hesam1111111111/tradeguard-oss/actions/workflows/ci.yml/badge.svg)](https://github.com/hesam1111111111/tradeguard-oss/actions/workflows/ci.yml)
4
+ [![Latest release](https://img.shields.io/github/v/release/hesam1111111111/tradeguard-oss)](https://github.com/hesam1111111111/tradeguard-oss/releases/latest)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
6
+ [![Python 3.10–3.13](https://img.shields.io/badge/Python-3.10%E2%80%933.13-blue.svg)](.github/workflows/ci.yml)
7
+
8
+ TradeGuard OSS is an open-source toolkit for validating trading journals, checking risk hygiene, and computing reproducible performance and journal-integrity diagnostics from closed trades.
9
+
10
+ > Status: active early development (`v0.7.0`). The project is intended for research, education, journaling, and system-quality checks. It is not financial advice and it does not place trades.
11
+
12
+ ## Why TradeGuard?
13
+
14
+ Trading journals often contain missing stop losses, inconsistent direction labels, invalid timestamps, duplicate records, incomplete position sizing, or performance statistics that cannot be reproduced. TradeGuard turns those checks into dependency-light, testable rules and deterministic reports.
15
+
16
+ ## Current capabilities
17
+
18
+ - Versioned CSV journal schema and row-level diagnostics
19
+ - Long/short PnL, initial risk, and R-multiple calculation
20
+ - Win rate, net PnL, expectancy, gross profit/loss, profit factor, breakeven count, best/worst trade, and closed-trade maximum drawdown
21
+ - Stop-loss and data-quality validation
22
+ - Deterministic SHA-256 journal fingerprints
23
+ - Exact duplicate-trade detection and blocking integrity diagnostics
24
+ - Entry-notional portfolio exposure by normalized symbol and side
25
+ - Gross/net notional exposure and configurable portfolio, symbol, and trade notional limits
26
+ - Stop-based historical risk budgets with explicit incomplete-data diagnostics
27
+ - Deterministic segmented analytics by symbol and side
28
+ - Optional deterministic closed-at grouping by calendar day or month
29
+ - Explicit mapped CSV imports with source-row provenance and rejection diagnostics
30
+ - Stable additive `tradeguard.report.v1` contract with explicit compatibility rules
31
+ - Human-readable or versioned JSON CLI output
32
+ - Deterministic JSON report export
33
+ - Automated tests across Python 3.10–3.13 plus distribution wheel smoke-install validation
34
+
35
+ ## Install for development
36
+
37
+ ```bash
38
+ git clone https://github.com/hesam1111111111/tradeguard-oss.git
39
+ cd tradeguard-oss
40
+ python -m venv .venv
41
+ # Windows: .venv\Scripts\activate
42
+ # macOS/Linux: source .venv/bin/activate
43
+ pip install -e .[dev]
44
+ pytest -q
45
+ ```
46
+
47
+ ## CSV format
48
+
49
+ Required columns:
50
+
51
+ ```text
52
+ symbol,side,entry,exit
53
+ ```
54
+
55
+ Optional columns:
56
+
57
+ ```text
58
+ stop_loss,quantity,opened_at,closed_at
59
+ ```
60
+
61
+ Example:
62
+
63
+ ```csv
64
+ symbol,side,entry,exit,stop_loss,quantity,opened_at,closed_at
65
+ BTCUSDT,long,60000,61500,59000,0.1,2026-01-01T10:00:00,2026-01-01T13:00:00
66
+ ETHUSDT,short,3200,3100,3260,1.0,2026-01-02T09:00:00,2026-01-02T12:00:00
67
+ ```
68
+
69
+ ## CLI
70
+
71
+ Native TradeGuard CSV:
72
+
73
+ ```bash
74
+ tradeguard examples/sample_journal.csv
75
+ tradeguard examples/sample_journal.csv --json
76
+ tradeguard examples/sample_journal.csv --output report.json
77
+ ```
78
+
79
+ Explicit mapped import from a differently named CSV:
80
+
81
+ ```bash
82
+ tradeguard examples/mapped_journal.csv \
83
+ --map symbol=Ticker \
84
+ --map side=Direction \
85
+ --map entry=OpenPrice \
86
+ --map exit=ClosePrice \
87
+ --map stop_loss=Stop \
88
+ --map quantity=Size \
89
+ --json
90
+ ```
91
+
92
+ Mappings are explicit by design. TradeGuard does not guess aliases or infer ambiguous columns. The report adds an `import` provenance section with source/imported/rejected row counts, the exact mapping, completeness, and source-indexed diagnostics. If mapped import is incomplete, metrics/risk/segments are suppressed rather than computed from a partial dataset.
93
+
94
+ Add deterministic temporal analytics based on the recorded `closed_at` value:
95
+
96
+ ```bash
97
+ tradeguard examples/sample_journal.csv --group-closed-by day --json
98
+ tradeguard examples/sample_journal.csv --group-closed-by month --output report.json
99
+ ```
100
+
101
+ The report retains the `tradeguard.report.v1` envelope and includes source, metrics, validation issues, journal fingerprint, structured integrity diagnostics, risk analysis, segmented analytics, and optional import provenance. Metrics are skipped when blocking validation, duplicate-record errors, or incomplete mapped import make analysis unsafe.
102
+
103
+ The stable machine-readable contract and compatibility rules are documented in [`docs/report-contract-v1.md`](docs/report-contract-v1.md).
104
+
105
+ ## Python API
106
+
107
+ ```python
108
+ from tradeguard import (
109
+ RiskLimits,
110
+ Trade,
111
+ aggregate_exposure,
112
+ analyze_by_closed_period,
113
+ analyze_trades,
114
+ check_risk_limits,
115
+ import_mapped_csv,
116
+ journal_fingerprint,
117
+ validate_trades,
118
+ )
119
+
120
+ trades = [Trade("BTCUSDT", "long", entry=60000, exit=61500, stop_loss=59000, quantity=0.1)]
121
+ print(validate_trades(trades))
122
+ print(journal_fingerprint(trades))
123
+ print(analyze_trades(trades))
124
+ print(analyze_by_closed_period(trades, "month"))
125
+ print(aggregate_exposure(trades))
126
+ print(check_risk_limits(trades, RiskLimits(max_gross_notional=10000)))
127
+
128
+ mapped = import_mapped_csv(
129
+ "examples/mapped_journal.csv",
130
+ {"symbol": "Ticker", "side": "Direction", "entry": "OpenPrice", "exit": "ClosePrice"},
131
+ )
132
+ print(mapped.imported_rows, mapped.rejected_rows)
133
+ ```
134
+
135
+ ### Exposure semantics
136
+
137
+ `aggregate_exposure` and notional risk limits use absolute `entry * quantity` values from the supplied journal. They describe historical entry-notional concentration; they are **not** live positions, mark-to-market exposure, margin usage, or broker account state.
138
+
139
+ ### Temporal semantics
140
+
141
+ Temporal grouping uses the recorded `closed_at` value exactly as supplied. TradeGuard does not guess or convert timezones; callers combining timestamps from different zones should normalize them before calendar grouping.
142
+
143
+ ## Development policy
144
+
145
+ Behavioral changes should arrive through scoped branches and pull requests with regression tests. CI runs the test suite across supported Python versions before changes are merged. Public examples must be synthetic or privacy-safe.
146
+
147
+ ## Roadmap
148
+
149
+ Near-term work includes additional offline import adapters, stronger source-data integrity diagnostics, and broader report-consumer fixtures. Live brokerage connectivity and order execution are outside the current core scope.
150
+
151
+ ## Contributing
152
+
153
+ Contributions are welcome. Please read [`CONTRIBUTING.md`](CONTRIBUTING.md), follow the [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md), open an issue for material changes, and include tests for behavioral changes.
154
+
155
+ Repository-maintainer review criteria and evidence are tracked in [`docs/oss-application-readiness.md`](docs/oss-application-readiness.md).
156
+
157
+ ## Security and privacy
158
+
159
+ TradeGuard does not require API keys for its core journal analytics. Do not commit broker credentials, exchange keys, private trade exports, or personal financial data. See [`SECURITY.md`](SECURITY.md).
160
+
161
+ ## License
162
+
163
+ MIT. See [`LICENSE`](LICENSE).
@@ -0,0 +1,26 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "tradeguard-oss"
7
+ version = "0.7.0"
8
+ description = "Open-source trade journal validation, risk analytics, and data-quality toolkit for traders and trading systems."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = {text = "MIT"}
12
+ authors = [{name = "TradeGuard OSS contributors"}]
13
+ dependencies = []
14
+
15
+ [project.optional-dependencies]
16
+ dev = ["pytest>=8.0", "build>=1.2"]
17
+
18
+ [project.scripts]
19
+ tradeguard = "tradeguard.cli:main"
20
+
21
+ [tool.setuptools.packages.find]
22
+ where = ["src"]
23
+
24
+ [tool.pytest.ini_options]
25
+ pythonpath = ["src"]
26
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,68 @@
1
+ """TradeGuard OSS public package."""
2
+
3
+ from .analytics import (
4
+ JournalMetrics,
5
+ JournalSegment,
6
+ TemporalAnalysis,
7
+ TemporalDiagnostic,
8
+ analyze_by_closed_period,
9
+ analyze_by_side,
10
+ analyze_by_symbol,
11
+ analyze_trades,
12
+ )
13
+ from .importers import ImportDiagnostic, ImportResult, import_mapped_csv
14
+ from .integrity import DuplicateTrade, find_duplicate_trades, journal_fingerprint
15
+ from .models import Trade
16
+ from .risk import (
17
+ Exposure,
18
+ InitialRiskAnalysis,
19
+ InitialRiskDiagnostic,
20
+ RiskBreach,
21
+ RiskBudget,
22
+ RiskBudgetBreach,
23
+ RiskBudgetEvaluation,
24
+ RiskLimits,
25
+ aggregate_exposure,
26
+ analyze_initial_risk,
27
+ check_risk_limits,
28
+ evaluate_risk_budget,
29
+ trade_initial_risk,
30
+ trade_notional,
31
+ )
32
+ from .validation import ValidationIssue, validate_trades
33
+
34
+ __all__ = [
35
+ "DuplicateTrade",
36
+ "Exposure",
37
+ "ImportDiagnostic",
38
+ "ImportResult",
39
+ "InitialRiskAnalysis",
40
+ "InitialRiskDiagnostic",
41
+ "JournalMetrics",
42
+ "JournalSegment",
43
+ "RiskBreach",
44
+ "RiskBudget",
45
+ "RiskBudgetBreach",
46
+ "RiskBudgetEvaluation",
47
+ "RiskLimits",
48
+ "TemporalAnalysis",
49
+ "TemporalDiagnostic",
50
+ "Trade",
51
+ "ValidationIssue",
52
+ "aggregate_exposure",
53
+ "analyze_by_closed_period",
54
+ "analyze_by_side",
55
+ "analyze_by_symbol",
56
+ "analyze_initial_risk",
57
+ "analyze_trades",
58
+ "check_risk_limits",
59
+ "evaluate_risk_budget",
60
+ "find_duplicate_trades",
61
+ "import_mapped_csv",
62
+ "journal_fingerprint",
63
+ "trade_initial_risk",
64
+ "trade_notional",
65
+ "validate_trades",
66
+ ]
67
+
68
+ __version__ = "0.7.0"
@@ -0,0 +1,158 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from datetime import datetime
5
+ from typing import Iterable, Literal
6
+
7
+ from .models import Trade
8
+
9
+
10
+ @dataclass(frozen=True, slots=True)
11
+ class JournalMetrics:
12
+ trades: int
13
+ wins: int
14
+ losses: int
15
+ breakeven: int
16
+ win_rate: float
17
+ net_pnl: float
18
+ gross_profit: float
19
+ gross_loss: float
20
+ profit_factor: float | None
21
+ expectancy: float
22
+ max_drawdown: float
23
+ average_r_multiple: float | None
24
+ best_trade: float | None
25
+ worst_trade: float | None
26
+
27
+
28
+ @dataclass(frozen=True, slots=True)
29
+ class JournalSegment:
30
+ key: str
31
+ metrics: JournalMetrics
32
+
33
+
34
+ @dataclass(frozen=True, slots=True)
35
+ class TemporalDiagnostic:
36
+ code: str
37
+ message: str
38
+ trade_index: int
39
+ symbol: str
40
+
41
+
42
+ @dataclass(frozen=True, slots=True)
43
+ class TemporalAnalysis:
44
+ basis: str
45
+ period: str
46
+ measured_trades: int
47
+ segments: tuple[JournalSegment, ...]
48
+ diagnostics: tuple[TemporalDiagnostic, ...]
49
+
50
+ @property
51
+ def complete(self) -> bool:
52
+ return not self.diagnostics
53
+
54
+
55
+ def _max_drawdown(pnls: list[float]) -> float:
56
+ equity = 0.0
57
+ peak = 0.0
58
+ max_dd = 0.0
59
+ for pnl in pnls:
60
+ equity += pnl
61
+ peak = max(peak, equity)
62
+ max_dd = max(max_dd, peak - equity)
63
+ return max_dd
64
+
65
+
66
+ def analyze_trades(trades: Iterable[Trade]) -> JournalMetrics:
67
+ items = list(trades)
68
+ pnls = [t.pnl for t in items]
69
+ wins = sum(1 for pnl in pnls if pnl > 0)
70
+ losses = sum(1 for pnl in pnls if pnl < 0)
71
+ breakeven = sum(1 for pnl in pnls if pnl == 0)
72
+ total = len(items)
73
+ gross_profit = sum(pnl for pnl in pnls if pnl > 0)
74
+ gross_loss = abs(sum(pnl for pnl in pnls if pnl < 0))
75
+ r_values = [r for t in items if (r := t.r_multiple) is not None]
76
+
77
+ return JournalMetrics(
78
+ trades=total,
79
+ wins=wins,
80
+ losses=losses,
81
+ breakeven=breakeven,
82
+ win_rate=(wins / total) if total else 0.0,
83
+ net_pnl=sum(pnls),
84
+ gross_profit=gross_profit,
85
+ gross_loss=gross_loss,
86
+ profit_factor=(gross_profit / gross_loss) if gross_loss else None,
87
+ expectancy=(sum(pnls) / total) if total else 0.0,
88
+ max_drawdown=_max_drawdown(pnls),
89
+ average_r_multiple=(sum(r_values) / len(r_values)) if r_values else None,
90
+ best_trade=max(pnls) if pnls else None,
91
+ worst_trade=min(pnls) if pnls else None,
92
+ )
93
+
94
+
95
+ def analyze_by_symbol(trades: Iterable[Trade]) -> tuple[JournalSegment, ...]:
96
+ groups: dict[str, list[Trade]] = {}
97
+ for trade in trades:
98
+ symbol = trade.symbol.strip().upper()
99
+ if not symbol:
100
+ raise ValueError("trade.symbol must not be empty")
101
+ groups.setdefault(symbol, []).append(trade)
102
+ return tuple(JournalSegment(key, analyze_trades(groups[key])) for key in sorted(groups))
103
+
104
+
105
+ def analyze_by_side(trades: Iterable[Trade]) -> tuple[JournalSegment, ...]:
106
+ groups: dict[str, list[Trade]] = {}
107
+ for trade in trades:
108
+ side = trade.normalized_side
109
+ groups.setdefault(side, []).append(trade)
110
+ return tuple(JournalSegment(key, analyze_trades(groups[key])) for key in sorted(groups))
111
+
112
+
113
+ def _period_key(timestamp: datetime, period: Literal["day", "month"]) -> str:
114
+ if period == "day":
115
+ return timestamp.date().isoformat()
116
+ if period == "month":
117
+ return f"{timestamp.year:04d}-{timestamp.month:02d}"
118
+ raise ValueError("period must be 'day' or 'month'")
119
+
120
+
121
+ def analyze_by_closed_period(
122
+ trades: Iterable[Trade], period: Literal["day", "month"] = "day"
123
+ ) -> TemporalAnalysis:
124
+ """Group historical trades by their recorded close timestamp.
125
+
126
+ Timestamp values are used exactly as supplied. TradeGuard does not infer or
127
+ convert a timezone; callers must normalize timestamps before analysis when
128
+ cross-timezone calendar grouping is required.
129
+ """
130
+ if period not in {"day", "month"}:
131
+ raise ValueError("period must be 'day' or 'month'")
132
+
133
+ groups: dict[str, list[Trade]] = {}
134
+ diagnostics: list[TemporalDiagnostic] = []
135
+ measured = 0
136
+ for index, trade in enumerate(trades):
137
+ symbol = trade.symbol.strip().upper() or "<UNKNOWN>"
138
+ timestamp = trade.closed_at
139
+ if timestamp is None:
140
+ diagnostics.append(TemporalDiagnostic(
141
+ code="missing_closed_at",
142
+ message="trade.closed_at is required for temporal analytics",
143
+ trade_index=index,
144
+ symbol=symbol,
145
+ ))
146
+ continue
147
+ key = _period_key(timestamp, period)
148
+ groups.setdefault(key, []).append(trade)
149
+ measured += 1
150
+
151
+ segments = tuple(JournalSegment(key, analyze_trades(groups[key])) for key in sorted(groups))
152
+ return TemporalAnalysis(
153
+ basis="recorded_closed_at_no_timezone_conversion",
154
+ period=period,
155
+ measured_trades=measured,
156
+ segments=segments,
157
+ diagnostics=tuple(diagnostics),
158
+ )