ghosalquant 0.1.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.
- ghosalquant-0.1.0/LICENSE +21 -0
- ghosalquant-0.1.0/PKG-INFO +113 -0
- ghosalquant-0.1.0/README.md +67 -0
- ghosalquant-0.1.0/pyproject.toml +65 -0
- ghosalquant-0.1.0/setup.cfg +4 -0
- ghosalquant-0.1.0/src/ghosalquant/__init__.py +10 -0
- ghosalquant-0.1.0/src/ghosalquant/backtest_ui/__init__.py +7 -0
- ghosalquant-0.1.0/src/ghosalquant/backtest_ui/__main__.py +10 -0
- ghosalquant-0.1.0/src/ghosalquant/backtest_ui/app.py +148 -0
- ghosalquant-0.1.0/src/ghosalquant/backtest_ui/runner.py +109 -0
- ghosalquant-0.1.0/src/ghosalquant/backtester/__init__.py +7 -0
- ghosalquant-0.1.0/src/ghosalquant/backtester/backtester_base.py +129 -0
- ghosalquant-0.1.0/src/ghosalquant/backtester/dataframe_backtester.py +95 -0
- ghosalquant-0.1.0/src/ghosalquant/bench/__init__.py +7 -0
- ghosalquant-0.1.0/src/ghosalquant/bench/benchmark_buy_and_hold.py +51 -0
- ghosalquant-0.1.0/src/ghosalquant/bench/benchmark_trend_vol_target.py +179 -0
- ghosalquant-0.1.0/src/ghosalquant/brokerage/__init__.py +26 -0
- ghosalquant-0.1.0/src/ghosalquant/brokerage/broker_base.py +123 -0
- ghosalquant-0.1.0/src/ghosalquant/brokerage/builder.py +42 -0
- ghosalquant-0.1.0/src/ghosalquant/brokerage/commissions.py +38 -0
- ghosalquant-0.1.0/src/ghosalquant/brokerage/fills.py +470 -0
- ghosalquant-0.1.0/src/ghosalquant/brokerage/ibkr.py +496 -0
- ghosalquant-0.1.0/src/ghosalquant/brokerage/open_orders.py +27 -0
- ghosalquant-0.1.0/src/ghosalquant/brokerage/order_book_sim.py +77 -0
- ghosalquant-0.1.0/src/ghosalquant/brokerage/simulated.py +80 -0
- ghosalquant-0.1.0/src/ghosalquant/brokerage/simulation.py +303 -0
- ghosalquant-0.1.0/src/ghosalquant/compatibility/legacybacktester.py +253 -0
- ghosalquant-0.1.0/src/ghosalquant/compatibility/plotManager.py +71 -0
- ghosalquant-0.1.0/src/ghosalquant/compatibility/test_strategy.py +63 -0
- ghosalquant-0.1.0/src/ghosalquant/compatibility/trade.py +70 -0
- ghosalquant-0.1.0/src/ghosalquant/core/__init__.py +59 -0
- ghosalquant-0.1.0/src/ghosalquant/core/blackscholes.py +169 -0
- ghosalquant-0.1.0/src/ghosalquant/core/contracts.py +155 -0
- ghosalquant-0.1.0/src/ghosalquant/core/metadata_keys.py +59 -0
- ghosalquant-0.1.0/src/ghosalquant/core/types.py +454 -0
- ghosalquant-0.1.0/src/ghosalquant/core/volatility.py +163 -0
- ghosalquant-0.1.0/src/ghosalquant/data_layer/__init__.py +29 -0
- ghosalquant-0.1.0/src/ghosalquant/data_layer/cboe_datasource.py +150 -0
- ghosalquant-0.1.0/src/ghosalquant/data_layer/dataLayer.py +127 -0
- ghosalquant-0.1.0/src/ghosalquant/data_layer/dataframe_datasource.py +76 -0
- ghosalquant-0.1.0/src/ghosalquant/data_layer/datasource_base.py +18 -0
- ghosalquant-0.1.0/src/ghosalquant/data_layer/datasource_factory.py +55 -0
- ghosalquant-0.1.0/src/ghosalquant/data_layer/dolt_datasource.py +159 -0
- ghosalquant-0.1.0/src/ghosalquant/data_layer/listEditor.py +372 -0
- ghosalquant-0.1.0/src/ghosalquant/data_layer/list_datasource.py +27 -0
- ghosalquant-0.1.0/src/ghosalquant/data_layer/live_alpaca_datasource.py +190 -0
- ghosalquant-0.1.0/src/ghosalquant/data_layer/live_ibkr_datasource.py +246 -0
- ghosalquant-0.1.0/src/ghosalquant/data_layer/live_polygon_datasource.py +264 -0
- ghosalquant-0.1.0/src/ghosalquant/data_layer/merge.py +52 -0
- ghosalquant-0.1.0/src/ghosalquant/data_layer/nse_datasource.py +126 -0
- ghosalquant-0.1.0/src/ghosalquant/data_layer/parquet_polygon_datasource.py +108 -0
- ghosalquant-0.1.0/src/ghosalquant/data_layer/recording_datasource.py +251 -0
- ghosalquant-0.1.0/src/ghosalquant/data_layer/shocks.py +99 -0
- ghosalquant-0.1.0/src/ghosalquant/data_layer/staticUpdater.py +408 -0
- ghosalquant-0.1.0/src/ghosalquant/data_layer/yfinance_datasource.py +60 -0
- ghosalquant-0.1.0/src/ghosalquant/data_layer/yfinance_multi_datasource.py +120 -0
- ghosalquant-0.1.0/src/ghosalquant/execution/__init__.py +16 -0
- ghosalquant-0.1.0/src/ghosalquant/execution/context.py +54 -0
- ghosalquant-0.1.0/src/ghosalquant/execution/execution_system.py +533 -0
- ghosalquant-0.1.0/src/ghosalquant/execution/sinks.py +122 -0
- ghosalquant-0.1.0/src/ghosalquant/execution/strategy_base.py +109 -0
- ghosalquant-0.1.0/src/ghosalquant/metrics/__init__.py +25 -0
- ghosalquant-0.1.0/src/ghosalquant/metrics/gates.py +150 -0
- ghosalquant-0.1.0/src/ghosalquant/metrics/risk_adjusted.py +196 -0
- ghosalquant-0.1.0/src/ghosalquant/portfolio/__init__.py +30 -0
- ghosalquant-0.1.0/src/ghosalquant/portfolio/attribution.py +81 -0
- ghosalquant-0.1.0/src/ghosalquant/portfolio/basic_portfolio.py +373 -0
- ghosalquant-0.1.0/src/ghosalquant/portfolio/hedging.py +87 -0
- ghosalquant-0.1.0/src/ghosalquant/portfolio/lots.py +63 -0
- ghosalquant-0.1.0/src/ghosalquant/portfolio/marks.py +58 -0
- ghosalquant-0.1.0/src/ghosalquant/portfolio/portfolio_base.py +104 -0
- ghosalquant-0.1.0/src/ghosalquant/portfolio/valuation.py +694 -0
- ghosalquant-0.1.0/src/ghosalquant/recording/__init__.py +111 -0
- ghosalquant-0.1.0/src/ghosalquant/recording/events.py +204 -0
- ghosalquant-0.1.0/src/ghosalquant/recording/intervals.py +236 -0
- ghosalquant-0.1.0/src/ghosalquant/recording/recorder.py +531 -0
- ghosalquant-0.1.0/src/ghosalquant/recording/storage.py +607 -0
- ghosalquant-0.1.0/src/ghosalquant/risk/__init__.py +56 -0
- ghosalquant-0.1.0/src/ghosalquant/risk/builder.py +151 -0
- ghosalquant-0.1.0/src/ghosalquant/risk/cash_allocation.py +285 -0
- ghosalquant-0.1.0/src/ghosalquant/risk/risk_manager.py +109 -0
- ghosalquant-0.1.0/src/ghosalquant/risk/rules/rule_confidence_sizer.py +32 -0
- ghosalquant-0.1.0/src/ghosalquant/risk/rules/rule_correlation_filter.py +105 -0
- ghosalquant-0.1.0/src/ghosalquant/risk/rules/rule_drawdown_killswitch.py +75 -0
- ghosalquant-0.1.0/src/ghosalquant/risk/rules/rule_integer_quantity.py +27 -0
- ghosalquant-0.1.0/src/ghosalquant/risk/rules/rule_kelly_sizer.py +74 -0
- ghosalquant-0.1.0/src/ghosalquant/risk/rules/rule_max_open_positions.py +27 -0
- ghosalquant-0.1.0/src/ghosalquant/risk/rules/rule_max_symbol_exposure.py +118 -0
- ghosalquant-0.1.0/src/ghosalquant/risk/rules/rule_regime_filter.py +50 -0
- ghosalquant-0.1.0/src/ghosalquant/risk/rules/rule_signal_quantity_cap.py +28 -0
- ghosalquant-0.1.0/src/ghosalquant/risk/rules/rule_symbol_capital_sizer.py +78 -0
- ghosalquant-0.1.0/src/ghosalquant/risk/rules/rule_tick_rounding.py +40 -0
- ghosalquant-0.1.0/src/ghosalquant/risk/sizing.py +46 -0
- ghosalquant-0.1.0/src/ghosalquant/risk/types.py +69 -0
- ghosalquant-0.1.0/src/ghosalquant/state/__init__.py +23 -0
- ghosalquant-0.1.0/src/ghosalquant/state/atomic.py +74 -0
- ghosalquant-0.1.0/src/ghosalquant/state/event_store.py +249 -0
- ghosalquant-0.1.0/src/ghosalquant/state/json_store.py +85 -0
- ghosalquant-0.1.0/src/ghosalquant/util/backtester_utils.py +118 -0
- ghosalquant-0.1.0/src/ghosalquant/util/config.py +522 -0
- ghosalquant-0.1.0/src/ghosalquant/util/deprecation.py +19 -0
- ghosalquant-0.1.0/src/ghosalquant/util/rounding.py +25 -0
- ghosalquant-0.1.0/src/ghosalquant/util/visualisation.py +162 -0
- ghosalquant-0.1.0/src/ghosalquant.egg-info/PKG-INFO +113 -0
- ghosalquant-0.1.0/src/ghosalquant.egg-info/SOURCES.txt +131 -0
- ghosalquant-0.1.0/src/ghosalquant.egg-info/dependency_links.txt +1 -0
- ghosalquant-0.1.0/src/ghosalquant.egg-info/entry_points.txt +2 -0
- ghosalquant-0.1.0/src/ghosalquant.egg-info/requires.txt +22 -0
- ghosalquant-0.1.0/src/ghosalquant.egg-info/top_level.txt +1 -0
- ghosalquant-0.1.0/tests/test_cboe_datasource.py +143 -0
- ghosalquant-0.1.0/tests/test_config.py +445 -0
- ghosalquant-0.1.0/tests/test_contracts.py +627 -0
- ghosalquant-0.1.0/tests/test_dolt_datasource.py +108 -0
- ghosalquant-0.1.0/tests/test_example_options_momentum.py +179 -0
- ghosalquant-0.1.0/tests/test_example_order_book.py +23 -0
- ghosalquant-0.1.0/tests/test_execution.py +429 -0
- ghosalquant-0.1.0/tests/test_execution_order_book.py +821 -0
- ghosalquant-0.1.0/tests/test_ibkr_broker.py +895 -0
- ghosalquant-0.1.0/tests/test_ibkr_real_client.py +278 -0
- ghosalquant-0.1.0/tests/test_legacy_backtester.py +96 -0
- ghosalquant-0.1.0/tests/test_limit_orders.py +184 -0
- ghosalquant-0.1.0/tests/test_live_sources.py +311 -0
- ghosalquant-0.1.0/tests/test_metrics.py +235 -0
- ghosalquant-0.1.0/tests/test_nse_datasource.py +110 -0
- ghosalquant-0.1.0/tests/test_order_book_sim_broker.py +495 -0
- ghosalquant-0.1.0/tests/test_order_book_types.py +233 -0
- ghosalquant-0.1.0/tests/test_recording.py +790 -0
- ghosalquant-0.1.0/tests/test_replace_orders.py +188 -0
- ghosalquant-0.1.0/tests/test_risk_rules.py +823 -0
- ghosalquant-0.1.0/tests/test_shocks.py +169 -0
- ghosalquant-0.1.0/tests/test_state.py +233 -0
- ghosalquant-0.1.0/tests/test_valuation.py +426 -0
- ghosalquant-0.1.0/tests/test_volatility.py +319 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 Ghosal Quants
|
|
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,113 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ghosalquant
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Event-driven quantitative trading research framework
|
|
5
|
+
Author: Ghosal Quants
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Ghosal-Quants/GhosalQuant
|
|
8
|
+
Project-URL: Documentation, https://github.com/Ghosal-Quants/GhosalQuant/blob/main/design/user_guide.md
|
|
9
|
+
Project-URL: Repository, https://github.com/Ghosal-Quants/GhosalQuant
|
|
10
|
+
Project-URL: Issues, https://github.com/Ghosal-Quants/GhosalQuant/issues
|
|
11
|
+
Keywords: quant,trading,backtesting,algorithmic-trading,finance
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: numpy
|
|
27
|
+
Requires-Dist: pandas
|
|
28
|
+
Requires-Dist: pytz
|
|
29
|
+
Requires-Dist: requests
|
|
30
|
+
Requires-Dist: yfinance
|
|
31
|
+
Requires-Dist: PyYAML
|
|
32
|
+
Requires-Dist: python-dotenv
|
|
33
|
+
Requires-Dist: pyarrow
|
|
34
|
+
Requires-Dist: fastparquet
|
|
35
|
+
Requires-Dist: ib_insync
|
|
36
|
+
Requires-Dist: websockets
|
|
37
|
+
Provides-Extra: alpaca
|
|
38
|
+
Requires-Dist: alpaca-py; extra == "alpaca"
|
|
39
|
+
Provides-Extra: plotting
|
|
40
|
+
Requires-Dist: matplotlib; extra == "plotting"
|
|
41
|
+
Requires-Dist: plotly; extra == "plotting"
|
|
42
|
+
Provides-Extra: ui
|
|
43
|
+
Requires-Dist: customtkinter; extra == "ui"
|
|
44
|
+
Requires-Dist: matplotlib; extra == "ui"
|
|
45
|
+
Dynamic: license-file
|
|
46
|
+
|
|
47
|
+
# GhosalQuant
|
|
48
|
+
Ghosal Fund | Quantitative Trading Team Repository
|
|
49
|
+
|
|
50
|
+
A modular Python framework for developing, backtesting, and executing quantitative trading strategies.
|
|
51
|
+
|
|
52
|
+
## Documentation
|
|
53
|
+
- [System Overview](https://github.com/Ghosal-Quants/GhosalQuant/blob/main/design/system-overview.md) - architecture + data flow
|
|
54
|
+
- [User Guide](https://github.com/Ghosal-Quants/GhosalQuant/blob/main/design/user_guide.md) - getting started + quick reference
|
|
55
|
+
|
|
56
|
+
## Installation
|
|
57
|
+
```bash
|
|
58
|
+
pip install ghosalquant
|
|
59
|
+
```
|
|
60
|
+
Optional extras: `ghosalquant[alpaca]` (Alpaca market data), `ghosalquant[plotting]` (matplotlib and plotly charts),
|
|
61
|
+
`ghosalquant[ui]` (the prototype backtest GUI, launched with `ghosalquant-ui`).
|
|
62
|
+
|
|
63
|
+
To work on the framework itself, install from source in editable mode:
|
|
64
|
+
```bash
|
|
65
|
+
git clone https://github.com/Ghosal-Quants/GhosalQuant.git
|
|
66
|
+
cd GhosalQuant
|
|
67
|
+
pip install -e .
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Quick Start
|
|
71
|
+
1. **Write a strategy** — subclass `Strategy`, implement `on_market_data(bar) -> Signal | None`
|
|
72
|
+
2. **Create a config** — see `examples/config.yaml`
|
|
73
|
+
3. **Run a backtest**:
|
|
74
|
+
```python
|
|
75
|
+
from ghosalquant.backtester.backtester_base import Backtester
|
|
76
|
+
from my_strategy import MyStrategy
|
|
77
|
+
|
|
78
|
+
kpms = Backtester.from_config(strategy=MyStrategy(), config_path="config.yaml").run()
|
|
79
|
+
print(kpms)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
See `examples/` for more examples for: DataFrame backtesting, strategy comparison, IBKR paper trading, and live data.
|
|
83
|
+
|
|
84
|
+
## Project Structure
|
|
85
|
+
```
|
|
86
|
+
src/ghosalquant/
|
|
87
|
+
core/ # Bar, Signal, Order, Fill, Trade types
|
|
88
|
+
execution/ # Strategy base, ExecutionSystem, signal sinks, benchmarks
|
|
89
|
+
data_layer/ # DataSource implementations (yfinance, Polygon, DataFrame, list)
|
|
90
|
+
backtester/ # Backtester, DataFrameBacktester
|
|
91
|
+
risk/ # RiskManager, rules (MaxOpenPositions, ConfidenceSizer)
|
|
92
|
+
portfolio/ # Portfolio protocol, BasicPortfolio
|
|
93
|
+
brokerage/ # Broker base, SimBroker, IBKRBroker
|
|
94
|
+
util/ # Config, visualisation, helpers
|
|
95
|
+
backtest_ui/ # Prototype GUI (the [ui] extra)
|
|
96
|
+
examples/ # Ready-to-run scripts and sample config
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Tom's Notes
|
|
100
|
+
|
|
101
|
+
### What are we actually trying to do?
|
|
102
|
+
- Find ideas for interesting strategies for a given asset class
|
|
103
|
+
- Implement the mathematical theory algorithmically
|
|
104
|
+
- Provide quantitative evidence that they work, over a period of time
|
|
105
|
+
- Execute the orders
|
|
106
|
+
|
|
107
|
+
### Some existing open source frameworks
|
|
108
|
+
- https://amor71.github.io/LiuAlgoTrader/
|
|
109
|
+
- https://www.backtrader.com/
|
|
110
|
+
- https://github.com/gbeced/basana (crypto orientated)
|
|
111
|
+
- https://github.com/panpanpandas/ultrafinance
|
|
112
|
+
- https://github.com/ematvey/pybacktest
|
|
113
|
+
- https://github.com/mhallsmoore/qstrader
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# GhosalQuant
|
|
2
|
+
Ghosal Fund | Quantitative Trading Team Repository
|
|
3
|
+
|
|
4
|
+
A modular Python framework for developing, backtesting, and executing quantitative trading strategies.
|
|
5
|
+
|
|
6
|
+
## Documentation
|
|
7
|
+
- [System Overview](https://github.com/Ghosal-Quants/GhosalQuant/blob/main/design/system-overview.md) - architecture + data flow
|
|
8
|
+
- [User Guide](https://github.com/Ghosal-Quants/GhosalQuant/blob/main/design/user_guide.md) - getting started + quick reference
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
```bash
|
|
12
|
+
pip install ghosalquant
|
|
13
|
+
```
|
|
14
|
+
Optional extras: `ghosalquant[alpaca]` (Alpaca market data), `ghosalquant[plotting]` (matplotlib and plotly charts),
|
|
15
|
+
`ghosalquant[ui]` (the prototype backtest GUI, launched with `ghosalquant-ui`).
|
|
16
|
+
|
|
17
|
+
To work on the framework itself, install from source in editable mode:
|
|
18
|
+
```bash
|
|
19
|
+
git clone https://github.com/Ghosal-Quants/GhosalQuant.git
|
|
20
|
+
cd GhosalQuant
|
|
21
|
+
pip install -e .
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
1. **Write a strategy** — subclass `Strategy`, implement `on_market_data(bar) -> Signal | None`
|
|
26
|
+
2. **Create a config** — see `examples/config.yaml`
|
|
27
|
+
3. **Run a backtest**:
|
|
28
|
+
```python
|
|
29
|
+
from ghosalquant.backtester.backtester_base import Backtester
|
|
30
|
+
from my_strategy import MyStrategy
|
|
31
|
+
|
|
32
|
+
kpms = Backtester.from_config(strategy=MyStrategy(), config_path="config.yaml").run()
|
|
33
|
+
print(kpms)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
See `examples/` for more examples for: DataFrame backtesting, strategy comparison, IBKR paper trading, and live data.
|
|
37
|
+
|
|
38
|
+
## Project Structure
|
|
39
|
+
```
|
|
40
|
+
src/ghosalquant/
|
|
41
|
+
core/ # Bar, Signal, Order, Fill, Trade types
|
|
42
|
+
execution/ # Strategy base, ExecutionSystem, signal sinks, benchmarks
|
|
43
|
+
data_layer/ # DataSource implementations (yfinance, Polygon, DataFrame, list)
|
|
44
|
+
backtester/ # Backtester, DataFrameBacktester
|
|
45
|
+
risk/ # RiskManager, rules (MaxOpenPositions, ConfidenceSizer)
|
|
46
|
+
portfolio/ # Portfolio protocol, BasicPortfolio
|
|
47
|
+
brokerage/ # Broker base, SimBroker, IBKRBroker
|
|
48
|
+
util/ # Config, visualisation, helpers
|
|
49
|
+
backtest_ui/ # Prototype GUI (the [ui] extra)
|
|
50
|
+
examples/ # Ready-to-run scripts and sample config
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Tom's Notes
|
|
54
|
+
|
|
55
|
+
### What are we actually trying to do?
|
|
56
|
+
- Find ideas for interesting strategies for a given asset class
|
|
57
|
+
- Implement the mathematical theory algorithmically
|
|
58
|
+
- Provide quantitative evidence that they work, over a period of time
|
|
59
|
+
- Execute the orders
|
|
60
|
+
|
|
61
|
+
### Some existing open source frameworks
|
|
62
|
+
- https://amor71.github.io/LiuAlgoTrader/
|
|
63
|
+
- https://www.backtrader.com/
|
|
64
|
+
- https://github.com/gbeced/basana (crypto orientated)
|
|
65
|
+
- https://github.com/panpanpandas/ultrafinance
|
|
66
|
+
- https://github.com/ematvey/pybacktest
|
|
67
|
+
- https://github.com/mhallsmoore/qstrader
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ghosalquant"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Event-driven quantitative trading research framework"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
authors = [{ name = "Ghosal Quants" }]
|
|
13
|
+
requires-python = ">=3.10"
|
|
14
|
+
keywords = ["quant", "trading", "backtesting", "algorithmic-trading", "finance"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Intended Audience :: Financial and Insurance Industry",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13",
|
|
26
|
+
"Topic :: Office/Business :: Financial :: Investment",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
dependencies = [
|
|
30
|
+
"numpy",
|
|
31
|
+
"pandas",
|
|
32
|
+
"pytz",
|
|
33
|
+
"requests",
|
|
34
|
+
"yfinance",
|
|
35
|
+
"PyYAML",
|
|
36
|
+
"python-dotenv",
|
|
37
|
+
"pyarrow",
|
|
38
|
+
"fastparquet",
|
|
39
|
+
"ib_insync",
|
|
40
|
+
"websockets"
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[project.optional-dependencies]
|
|
44
|
+
alpaca = ["alpaca-py"]
|
|
45
|
+
plotting = ["matplotlib", "plotly"]
|
|
46
|
+
ui = ["customtkinter", "matplotlib"]
|
|
47
|
+
|
|
48
|
+
[project.gui-scripts]
|
|
49
|
+
ghosalquant-ui = "ghosalquant.backtest_ui.app:main"
|
|
50
|
+
|
|
51
|
+
[project.urls]
|
|
52
|
+
Homepage = "https://github.com/Ghosal-Quants/GhosalQuant"
|
|
53
|
+
Documentation = "https://github.com/Ghosal-Quants/GhosalQuant/blob/main/design/user_guide.md"
|
|
54
|
+
Repository = "https://github.com/Ghosal-Quants/GhosalQuant"
|
|
55
|
+
Issues = "https://github.com/Ghosal-Quants/GhosalQuant/issues"
|
|
56
|
+
|
|
57
|
+
[tool.setuptools.dynamic]
|
|
58
|
+
version = { attr = "ghosalquant.__version__" }
|
|
59
|
+
|
|
60
|
+
[tool.setuptools.packages.find]
|
|
61
|
+
where = ["src"]
|
|
62
|
+
include = ["ghosalquant*"]
|
|
63
|
+
|
|
64
|
+
[tool.pytest.ini_options]
|
|
65
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from .runner import run_from_config
|
|
5
|
+
|
|
6
|
+
if __name__ == "__main__":
|
|
7
|
+
if len(sys.argv) < 3:
|
|
8
|
+
print("Usage: python -m ghosalquant.backtest_ui <config.yaml> <strategy.py>")
|
|
9
|
+
raise SystemExit(2)
|
|
10
|
+
raise SystemExit(run_from_config(sys.argv[1], sys.argv[2]))
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import threading
|
|
5
|
+
import subprocess
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from queue import Queue, Empty
|
|
8
|
+
|
|
9
|
+
import customtkinter as ctk
|
|
10
|
+
from tkinter import filedialog, messagebox
|
|
11
|
+
|
|
12
|
+
from . import project_root
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
WARNING: VIBED because I hate UI development and this is a prototype for now
|
|
16
|
+
|
|
17
|
+
This is a rough experimental prototype of a UI to abstract backtesting from QRs.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class App(ctk.CTk):
|
|
22
|
+
def __init__(self) -> None:
|
|
23
|
+
super().__init__()
|
|
24
|
+
self.title("GhosalQuant Backtest (Prototype)")
|
|
25
|
+
self.geometry("900x600")
|
|
26
|
+
|
|
27
|
+
self.root = project_root()
|
|
28
|
+
self.default_cfg = self.root / "examples" / "config.yaml"
|
|
29
|
+
|
|
30
|
+
self.cfg_var = ctk.StringVar(value=str(self.default_cfg) if self.default_cfg.exists() else "")
|
|
31
|
+
|
|
32
|
+
self.log_q: Queue[str] = Queue()
|
|
33
|
+
|
|
34
|
+
top = ctk.CTkFrame(self)
|
|
35
|
+
top.pack(fill="x", padx=12, pady=12)
|
|
36
|
+
|
|
37
|
+
row1 = ctk.CTkFrame(top)
|
|
38
|
+
row1.pack(fill="x")
|
|
39
|
+
|
|
40
|
+
row2 = ctk.CTkFrame(top)
|
|
41
|
+
row2.pack(fill="x")
|
|
42
|
+
|
|
43
|
+
# Config + Run
|
|
44
|
+
ctk.CTkLabel(row1, text="Config:").pack(side="left", padx=(10, 8), pady=10)
|
|
45
|
+
self.cfg_entry = ctk.CTkEntry(row1, textvariable=self.cfg_var, width=560)
|
|
46
|
+
self.cfg_entry.pack(side="left", padx=8, pady=10)
|
|
47
|
+
ctk.CTkButton(row1, text="Browse…", command=self.browse).pack(side="left", padx=8, pady=10)
|
|
48
|
+
self.run_btn = ctk.CTkButton(row1, text="Run", command=self.run)
|
|
49
|
+
self.run_btn.pack(side="left", padx=8, pady=10)
|
|
50
|
+
|
|
51
|
+
# Strategy
|
|
52
|
+
self.strategy_var = ctk.StringVar(value="")
|
|
53
|
+
ctk.CTkLabel(row2, text="Strategy:").pack(side="left", padx=(10, 8), pady=10)
|
|
54
|
+
self.strategy_entry = ctk.CTkEntry(row2, textvariable=self.strategy_var, width=560)
|
|
55
|
+
self.strategy_entry.pack(side="left", padx=8, pady=10)
|
|
56
|
+
ctk.CTkButton(row2, text="Browse…", command=self.browse_strategy).pack(side="left", padx=8, pady=10)
|
|
57
|
+
|
|
58
|
+
self.log = ctk.CTkTextbox(self, wrap="none")
|
|
59
|
+
self.log.pack(fill="both", expand=True, padx=12, pady=(0, 12))
|
|
60
|
+
|
|
61
|
+
self.after(100, self.drain_logs)
|
|
62
|
+
|
|
63
|
+
def browse(self) -> None:
|
|
64
|
+
p = filedialog.askopenfilename(
|
|
65
|
+
title="Select config.yaml",
|
|
66
|
+
filetypes=[("YAML files", "*.yaml *.yml"), ("All files", "*.*")]
|
|
67
|
+
)
|
|
68
|
+
if p:
|
|
69
|
+
self.cfg_var.set(p)
|
|
70
|
+
|
|
71
|
+
def browse_strategy(self) -> None:
|
|
72
|
+
p = filedialog.askopenfilename(
|
|
73
|
+
title="Select strategy .py",
|
|
74
|
+
filetypes=[("Python files", "*.py"), ("All files", "*.*")]
|
|
75
|
+
)
|
|
76
|
+
if p:
|
|
77
|
+
self.strategy_var.set(p)
|
|
78
|
+
|
|
79
|
+
def append(self, line: str) -> None:
|
|
80
|
+
self.log.insert("end", line + "\n")
|
|
81
|
+
self.log.see("end")
|
|
82
|
+
|
|
83
|
+
def drain_logs(self) -> None:
|
|
84
|
+
try:
|
|
85
|
+
while True:
|
|
86
|
+
self.append(self.log_q.get_nowait())
|
|
87
|
+
except Empty:
|
|
88
|
+
pass
|
|
89
|
+
self.after(100, self.drain_logs)
|
|
90
|
+
|
|
91
|
+
def run(self) -> None:
|
|
92
|
+
cfg = Path(self.cfg_var.get().strip())
|
|
93
|
+
if not cfg.exists():
|
|
94
|
+
messagebox.showerror("Missing config", f"Config not found:\n{cfg}")
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
strategy = Path(self.strategy_var.get().strip())
|
|
98
|
+
if not strategy.exists():
|
|
99
|
+
messagebox.showerror("Missing strategy", f"Strategy not found:\n{strategy}")
|
|
100
|
+
self.run_btn.configure(state="normal")
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
self.run_btn.configure(state="disabled")
|
|
104
|
+
self.append(f"Running with config: {cfg}")
|
|
105
|
+
|
|
106
|
+
strategy = Path(self.strategy_var.get().strip()) # add a strategy picker var in UI
|
|
107
|
+
cmd = [sys.executable, "-m", "ghosalquant.backtest_ui", str(cfg), str(strategy)]
|
|
108
|
+
|
|
109
|
+
def worker() -> None:
|
|
110
|
+
try:
|
|
111
|
+
proc = subprocess.Popen(
|
|
112
|
+
cmd,
|
|
113
|
+
cwd=str(self.root),
|
|
114
|
+
stdout=subprocess.PIPE,
|
|
115
|
+
stderr=subprocess.STDOUT,
|
|
116
|
+
text=True,
|
|
117
|
+
)
|
|
118
|
+
assert proc.stdout is not None
|
|
119
|
+
for line in proc.stdout:
|
|
120
|
+
self.log_q.put(line.rstrip("\n"))
|
|
121
|
+
rc = proc.wait()
|
|
122
|
+
self.log_q.put(f"[done] exit code = {rc}")
|
|
123
|
+
except Exception as e:
|
|
124
|
+
self.log_q.put(f"[error] {e}")
|
|
125
|
+
finally:
|
|
126
|
+
self.log_q.put("__enable__")
|
|
127
|
+
|
|
128
|
+
def enable_later() -> None:
|
|
129
|
+
# wait for the marker then re-enable the button in UI thread
|
|
130
|
+
while True:
|
|
131
|
+
msg = self.log_q.get()
|
|
132
|
+
if msg == "__enable__":
|
|
133
|
+
self.run_btn.configure(state="normal")
|
|
134
|
+
break
|
|
135
|
+
else:
|
|
136
|
+
self.append(msg)
|
|
137
|
+
|
|
138
|
+
threading.Thread(target=worker, daemon=True).start()
|
|
139
|
+
threading.Thread(target=enable_later, daemon=True).start()
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def main() -> None:
|
|
143
|
+
ctk.set_appearance_mode("System")
|
|
144
|
+
App().mainloop()
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
if __name__ == "__main__":
|
|
148
|
+
main()
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Type
|
|
6
|
+
import importlib.util
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
9
|
+
import yfinance as yf
|
|
10
|
+
from matplotlib import pyplot as plt
|
|
11
|
+
|
|
12
|
+
from ghosalquant.backtester.dataframe_backtester import DataFrameBacktester
|
|
13
|
+
from ghosalquant.execution.strategy_base import Strategy
|
|
14
|
+
from ghosalquant.util.visualisation import plot_equity_curve
|
|
15
|
+
|
|
16
|
+
from . import project_root
|
|
17
|
+
|
|
18
|
+
def _safe_stem(s: str) -> str:
|
|
19
|
+
# sanitiser
|
|
20
|
+
chars: list[str] = []
|
|
21
|
+
for ch in s:
|
|
22
|
+
if ch.isalnum() or ch in ("-", "_"):
|
|
23
|
+
chars.append(ch)
|
|
24
|
+
else:
|
|
25
|
+
chars.append("_")
|
|
26
|
+
|
|
27
|
+
stem = "".join(chars).strip("_")
|
|
28
|
+
return stem or "run"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _load_strategy_from_file(path: Path) -> Strategy:
|
|
32
|
+
spec = importlib.util.spec_from_file_location(path.stem, str(path))
|
|
33
|
+
if spec is None or spec.loader is None:
|
|
34
|
+
raise RuntimeError(f"Could not import strategy file: {path}")
|
|
35
|
+
|
|
36
|
+
mod = importlib.util.module_from_spec(spec)
|
|
37
|
+
spec.loader.exec_module(mod) # type: ignore[assignment]
|
|
38
|
+
|
|
39
|
+
# Explicit export as STRATEGY_CLASS
|
|
40
|
+
if hasattr(mod, "STRATEGY_CLASS"):
|
|
41
|
+
cls = getattr(mod, "STRATEGY_CLASS")
|
|
42
|
+
if isinstance(cls, type) and issubclass(cls, Strategy):
|
|
43
|
+
return cls()
|
|
44
|
+
raise TypeError("STRATEGY_CLASS exists but is not a Strategy subclass.")
|
|
45
|
+
|
|
46
|
+
# As a fallback, find first Strategy subclass in module
|
|
47
|
+
for obj in mod.__dict__.values():
|
|
48
|
+
if isinstance(obj, type) and issubclass(obj, Strategy) and obj is not Strategy:
|
|
49
|
+
return obj()
|
|
50
|
+
|
|
51
|
+
raise ValueError(
|
|
52
|
+
"No strategy found. Define STRATEGY_CLASS = YourStrategy, "
|
|
53
|
+
"or include a Strategy subclass in the file."
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def run_from_config(config_path: str | Path, strategy_path: str | Path) -> int:
|
|
58
|
+
cfg_path = Path(config_path).resolve()
|
|
59
|
+
strat_path = Path(strategy_path).resolve()
|
|
60
|
+
|
|
61
|
+
if not cfg_path.exists():
|
|
62
|
+
print(f"Config not found: {cfg_path}")
|
|
63
|
+
return 2
|
|
64
|
+
if not strat_path.exists():
|
|
65
|
+
print(f"Strategy not found: {strat_path}")
|
|
66
|
+
return 2
|
|
67
|
+
|
|
68
|
+
cfg: dict[str, Any] = yaml.safe_load(cfg_path.read_text(encoding="utf-8")) or {}
|
|
69
|
+
system = cfg.get("SYSTEM", {}) or {}
|
|
70
|
+
tr = cfg.get("TIME_RANGE", {}) or {}
|
|
71
|
+
|
|
72
|
+
symbols = system.get("symbols", ["AAPL"]) or ["AAPL"]
|
|
73
|
+
symbol = symbols[0] # prototype: single symbol
|
|
74
|
+
|
|
75
|
+
start_date = tr.get("start_date", "2024-01-01")
|
|
76
|
+
end_date = tr.get("end_date", "2025-12-31")
|
|
77
|
+
|
|
78
|
+
print("UI Runner: DataFrameBacktester")
|
|
79
|
+
print(f"Config: {cfg_path}")
|
|
80
|
+
print(f"Strategy: {strat_path}")
|
|
81
|
+
print(f"Symbol: {symbol}")
|
|
82
|
+
print(f"Range: {start_date} -> {end_date}")
|
|
83
|
+
|
|
84
|
+
df = yf.download(symbol, start=start_date, end=end_date, auto_adjust=True, progress=False)
|
|
85
|
+
|
|
86
|
+
strategy = _load_strategy_from_file(strat_path)
|
|
87
|
+
backtester = DataFrameBacktester(strategy=strategy, dataframe=df, config_path=str(cfg_path))
|
|
88
|
+
|
|
89
|
+
kpms = backtester.run()
|
|
90
|
+
print("\nKEY PERFORMANCE METRICS:\n" + str(kpms))
|
|
91
|
+
print("REALISED PNL:", backtester.realised_pnl)
|
|
92
|
+
|
|
93
|
+
# Plot and save to file
|
|
94
|
+
plot_equity_curve(backtester.equity_curve, show_realised_unrealised=True)
|
|
95
|
+
|
|
96
|
+
out_dir = project_root() / "ui_outputs"
|
|
97
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
98
|
+
|
|
99
|
+
strategy_name = _safe_stem(strat_path.stem)
|
|
100
|
+
symbol_name = _safe_stem(symbol)
|
|
101
|
+
now = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
|
102
|
+
out_path = out_dir / f"equity_curve_{now}_{symbol_name}_{strategy_name}.png"
|
|
103
|
+
|
|
104
|
+
plt.savefig(out_path, dpi=150, bbox_inches="tight")
|
|
105
|
+
plt.close()
|
|
106
|
+
|
|
107
|
+
print(f"Saved plot: {out_path}")
|
|
108
|
+
|
|
109
|
+
return 0
|