forexgen 2.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.
Files changed (39) hide show
  1. forexgen-2.1.0/LICENSE +17 -0
  2. forexgen-2.1.0/PKG-INFO +303 -0
  3. forexgen-2.1.0/README.md +267 -0
  4. forexgen-2.1.0/forexgen/__init__.py +27 -0
  5. forexgen-2.1.0/forexgen/__main__.py +5 -0
  6. forexgen-2.1.0/forexgen/api.py +94 -0
  7. forexgen-2.1.0/forexgen/calibration.py +357 -0
  8. forexgen-2.1.0/forexgen/cli.py +108 -0
  9. forexgen-2.1.0/forexgen/costs.py +66 -0
  10. forexgen-2.1.0/forexgen/diagnostics.py +210 -0
  11. forexgen-2.1.0/forexgen/engine.py +313 -0
  12. forexgen-2.1.0/forexgen/events.py +269 -0
  13. forexgen-2.1.0/forexgen/export.py +81 -0
  14. forexgen-2.1.0/forexgen/factors.py +114 -0
  15. forexgen-2.1.0/forexgen/fixtures.py +174 -0
  16. forexgen-2.1.0/forexgen/marketcal.py +112 -0
  17. forexgen-2.1.0/forexgen/markets.py +76 -0
  18. forexgen-2.1.0/forexgen/pipeline.py +289 -0
  19. forexgen-2.1.0/forexgen/profile_data/EURUSD.json +135 -0
  20. forexgen-2.1.0/forexgen/profiles.py +286 -0
  21. forexgen-2.1.0/forexgen/regimes.py +115 -0
  22. forexgen-2.1.0/forexgen/report.py +135 -0
  23. forexgen-2.1.0/forexgen/scenarios.py +152 -0
  24. forexgen-2.1.0/forexgen/ticks.py +81 -0
  25. forexgen-2.1.0/forexgen/validation.py +100 -0
  26. forexgen-2.1.0/forexgen.egg-info/PKG-INFO +303 -0
  27. forexgen-2.1.0/forexgen.egg-info/SOURCES.txt +37 -0
  28. forexgen-2.1.0/forexgen.egg-info/dependency_links.txt +1 -0
  29. forexgen-2.1.0/forexgen.egg-info/entry_points.txt +2 -0
  30. forexgen-2.1.0/forexgen.egg-info/requires.txt +2 -0
  31. forexgen-2.1.0/forexgen.egg-info/top_level.txt +1 -0
  32. forexgen-2.1.0/pyproject.toml +34 -0
  33. forexgen-2.1.0/setup.cfg +4 -0
  34. forexgen-2.1.0/tests/test_calendar_repro.py +76 -0
  35. forexgen-2.1.0/tests/test_compat.py +109 -0
  36. forexgen-2.1.0/tests/test_events_scenarios.py +96 -0
  37. forexgen-2.1.0/tests/test_multi_cross.py +52 -0
  38. forexgen-2.1.0/tests/test_ticks_validation_profiles.py +140 -0
  39. forexgen-2.1.0/tests/test_v21_features.py +202 -0
forexgen-2.1.0/LICENSE ADDED
@@ -0,0 +1,17 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Henry Nyoike
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.
@@ -0,0 +1,303 @@
1
+ Metadata-Version: 2.4
2
+ Name: forexgen
3
+ Version: 2.1.0
4
+ Summary: Offline synthetic forex and gold data generator for indicator testing and strategy backtesting
5
+ Author: Henry Nyoike
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Henry Nyoike
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
23
+
24
+ Classifier: Development Status :: 5 - Production/Stable
25
+ Classifier: Intended Audience :: Developers
26
+ Classifier: License :: OSI Approved :: MIT License
27
+ Classifier: Programming Language :: Python :: 3
28
+ Classifier: Topic :: Scientific/Engineering
29
+ Classifier: Topic :: Office/Business :: Financial :: Investment
30
+ Requires-Python: >=3.10
31
+ Description-Content-Type: text/markdown
32
+ License-File: LICENSE
33
+ Requires-Dist: numpy>=1.26
34
+ Requires-Dist: pandas>=2.2
35
+ Dynamic: license-file
36
+
37
+ # forexgen 2.1
38
+
39
+ Offline synthetic **forex and gold** data generator for **indicator testing and strategy backtesting**
40
+ (and, secondarily, producing training data). Pure Python, **numpy + pandas only**, no internet, no GUI.
41
+
42
+ * 11 markets: EURUSD GBPUSD USDJPY AUDUSD NZDUSD USDCHF USDCAD EURGBP EURJPY GBPJPY XAUUSD (+ your own).
43
+ * **Different data on every run by default**; the seed used is recorded so any run can be reproduced.
44
+ * Real market structure: volatility clustering, fat tails, DST-aware sessions, scheduled news (NFP, CPI,
45
+ FOMC, ECB, BoE ...), regimes, weekend gaps, spreads that widen at news/rollover, intrabar ticks with bid/ask.
46
+ * Several markets simulated **jointly** (shared factors, correlations that rise in crises); crosses derived
47
+ **exactly** from their legs.
48
+ * Backtest-validation tools: luck-only ("null") paths, planted-edge paths, Monte Carlo of your strategy.
49
+ * `calibrate()` fits a market to a real HistData/MetaTrader M1 file; `calibrate_many()` does it for several markets and
50
+ months at once and measures their correlations; `scorecard()` compares synthetic with real.
51
+ * **New in 2.1:** `selftest()` (is the generator itself giving strategies a free edge?), `robustness_report(strategy)`
52
+ (one-page verdict), indicator edge-case `fixtures` + `indicator_stress_test`, daily bars cut at the **17:00 New York
53
+ close** (`daily_anchor="ny"`), **HistData/MetaTrader file writers** (`export_histdata`, `generate_histdata`),
54
+ correlation control (`set_correlation`), and shared microstructure noise so tightly coupled pairs (EURUSD/USDCHF)
55
+ correlate more like real markets.
56
+
57
+ > **Read this first.** Only **EURUSD** was calibrated to real data (one real month, Aug 2026, 28,643 M1 bars).
58
+ > Every other market is built from **documented assumptions** about its typical behaviour, scaled from the
59
+ > EURUSD baseline, and says so in `forexgen.describe("GBPUSD")`. Synthetic data can test your **logic,
60
+ > robustness and cost sensitivity**; it **cannot prove a strategy makes money live** (no real news,
61
+ > central-bank behaviour or regime history). See [Limitations](#limitations).
62
+
63
+ ## Install
64
+
65
+ ```bash
66
+ pip install forexgen
67
+ python -m pytest tests # optional
68
+ ```
69
+
70
+ ## Quick start
71
+
72
+ ```python
73
+ import forexgen
74
+
75
+ df = forexgen.get_data("EURUSD", "M15", "3m") # different every call
76
+ print(df.attrs["seed"]) # ... but reproducible
77
+ df = forexgen.get_data("EURUSD", "M15", "3m", seed=df.attrs["seed"])
78
+
79
+ forexgen.generate_csv("GBPUSD", "H1", "1y", seed=7, folder="data") # data/GBPUSD_H1_1y_seed7.csv
80
+ data = forexgen.get_multi(["EURUSD", "GBPUSD", "XAUUSD", "EURGBP"], "M15", "1y", seed=1)
81
+ forexgen.describe("XAUUSD") # calibrated vs assumed
82
+ ```
83
+
84
+ Command line: `python -m forexgen EURUSD M15 3m --seed 7 --out data` (`--list`, `--profile`, `--scenario`,
85
+ `--sparse`, `--labels`, `--ticks`, `--multi EURUSD,GBPUSD`, `--describe`, `--calibrate FILE`). The seed used is printed.
86
+
87
+ ## Output format (unchanged from v1)
88
+
89
+ `time` index/column = **naive UTC**, format `%Y-%m-%d %H:%M:%S`; columns `open, high, low, close, volume, spread_pips`
90
+ (`volume` = tick volume, int). Prices are bid-style OHLC rounded to the market's digits. All timeframes are
91
+ aggregated from the same M1 path, so they agree.
92
+
93
+ ## API
94
+
95
+ ### `get_data(market, timeframe="M15", duration="30d", start="2024-01-01 00:00", end=None, seed=None, start_price=None, **options)`
96
+
97
+ | argument | meaning |
98
+ |---|---|
99
+ | `market` | e.g. `"EURUSD"`, `"XAUUSD"` (case/slash-insensitive: `"eur/usd"`). `list_markets()` |
100
+ | `timeframe` | `M1 M5 M15 M30 H1 H4 D1 W1` (any `M<n>`/`H<n>`; also `15m`, `1h`, `daily`) |
101
+ | `duration` | `30d`, `2w`, `3m`, `1y`, `12h`, `90min` (**market time**: 1w = 5 days, 1m = 21 days, 1y = 260 days) |
102
+ | `start`, `end` | naive UTC timestamps; `end` overrides `duration` |
103
+ | `seed` | `None` (default): fresh data every call. An integer reproduces the data exactly. Stored in `df.attrs["seed"]` |
104
+ | `start_price` | override the first price (for crosses it adjusts the first leg) |
105
+ | `profile` | `"typical"` (default), `"calm_aug2026"` (the real calm month's level), `"stressed"`, or a `Profile` |
106
+ | `scenario`, `scenario_at`, `scenario_strength`, `scenario_direction`, `scenario_markets` | inject a stress scenario (below) |
107
+ | `events` | `"default"` (rule calendar), `"none"`, or a DataFrame / CSV of your own events |
108
+ | `sparse` | `True`: broker-like flat bars and missing minutes (measured EURUSD rates) |
109
+ | `return_labels` | adds `regime`, `event_flag`, `true_vol` columns |
110
+ | `edge` | `dict(kind="momentum"\|"mean_reversion", strength=0.02, horizon="1h")` plants a known edge |
111
+ | `**model` | v1 options still accepted: `garch=(a,b)`, `t_df`, `jump_rate_per_day`, `jump_sigma`, `weekend_gap`, `kappa`, `drift`, `usd_factor`; plus `vol_scale` |
112
+
113
+ How the v1 options map: `garch` sets the GARCH alpha/beta; `t_df` the Student-t tail; `jump_rate_per_day` /
114
+ `jump_sigma` the **residual unscheduled** shock process (scheduled news now comes from the calendar);
115
+ `weekend_gap` is the weekend-gap standard deviation in per-minute sigmas; `kappa` the mean-reversion speed;
116
+ `drift` an annual log drift; `usd_factor=False` removes the cross-market factor.
117
+
118
+ ### `generate_csv(market, timeframe="M15", duration="30d", path=None, folder="forex_data", **kwargs)`
119
+ Writes the CSV and returns its path. Default name `<folder>/<SYMBOL>_<TF>_<duration>_seed<seed>.csv`
120
+ (`..._to-<digits of end>_...` when `end` is given). With `seed=None` the seed actually used is in the name.
121
+
122
+ ### `get_multi(markets, timeframe, duration, start, end, seed, profile, as_frame=False, **options)`
123
+ Joint simulation. Returns `{symbol: DataFrame}` (or one MultiIndex-column DataFrame). **A market inside
124
+ `get_multi` equals `get_data(market, seed=seed)`** for the same arguments. Crosses are derived exactly:
125
+ `EURGBP = EURUSD / GBPUSD`, `EURJPY = EURUSD * USDJPY`, `GBPJPY = GBPUSD * USDJPY`.
126
+
127
+ ### `get_ticks(market, duration="1d", start, end, seed, profile, ticks_per_minute="auto", ...)`
128
+ Bid/ask ticks inside every M1 bar on a Brownian-bridge path **conditioned on the bar's OHLC** (it visits the
129
+ high and low in a defined order), so stop/target order inside a bar is decided by a path, not a guess. Bars
130
+ rebuilt from the bid ticks equal `get_data(..., "M1")` for the same seed. ~100 µs per minute bar (off by default).
131
+
132
+ ### Events: `get_events`, `register_event`, `set_events`
133
+ `get_events("EURUSD", start, end)` returns the calendar used for that market (with its sensitivity).
134
+ `register_event(name, time_utc, importance=3, currencies=("USD",))` adds one event; `set_events(df_or_csv)`
135
+ replaces the rule calendar (columns `time_utc, name, importance, currencies`); `set_events(None)` restores it.
136
+ **All default dates are rule-based approximations, not the official calendar:**
137
+
138
+ | event | rule (local time) | importance |
139
+ |---|---|---|
140
+ | US NFP | first Friday 08:30 NY | 5 |
141
+ | US CPI | Wednesday in 11th-17th, 08:30 NY | 4 |
142
+ | US FOMC | 8/yr, Wednesday 14:00 NY | 5 |
143
+ | US retail sales / PCE / ISM / UMich | 08:30 or 10:00 NY | 2-3 |
144
+ | Jackson Hole | Friday in 22nd-28th of August, 10:00 NY | 4 |
145
+ | ECB | 8/yr, Thursday 14:15 Frankfurt | 4 |
146
+ | BoE | 8/yr, Thursday 12:00 London | 4 |
147
+ | BoJ, RBA, RBNZ, SNB, BoC | 3-8/yr, local release times | 3 |
148
+ | US jobless claims | every Thursday 08:30 NY | 1 |
149
+
150
+ Reaction (assumed shape; sizes anchored to the 8 events observed in the real file): vol x0.75 in the 30 minutes
151
+ before, a random-sign heavy-tailed shock (80/12/5/3% over the first 4 minutes), power-law volatility decay for
152
+ ~4 hours, spread widening. USD events move every USD pair (opposite sign for USDxxx vs xxxUSD, x1.5 for gold);
153
+ other currencies' events move the pairs that contain them.
154
+
155
+ ### Scenarios
156
+ `scenario=` one of `flash_crash, peg_break, rate_shock, trend_up, trend_down, range, high_vol, low_liquidity,
157
+ gap, intervention, crisis`, placed at `scenario_at` (timestamp, or a fraction 0..1 of the data) and scaled by
158
+ `scenario_strength`. They are **parameter transformations of the base model** (jumps, volatility/spread
159
+ multipliers, drift, mean reversion, forced global stress), documented in `forexgen/scenarios.py`.
160
+ `crisis` also forces the global stress state, so correlations rise toward 1 in `get_multi`.
161
+ `scenario_markets=[...]` restricts which markets get the scenario; `scenario_direction="up"|"down"|"auto"`.
162
+
163
+ ### Backtest validation tools
164
+ ```python
165
+ null = forexgen.monte_carlo(strategy, "EURUSD", "M5", "1m", n=200, mode="null") # luck only
166
+ edge = forexgen.monte_carlo(strategy, "EURUSD", "M5", "1m", n=50, mode="edge") # planted edge
167
+ reg = forexgen.monte_carlo(strategy, "EURUSD", "M5", "1m", n=50, mode="regular")
168
+ forexgen.percentile_in_null(my_real_backtest_return, null) # >95 = unlikely luck
169
+ paths = forexgen.get_null_paths("EURUSD", "H1", "3m", n=100) # the raw null paths
170
+ ```
171
+ `strategy(df) -> pandas.Series` of target positions in [-1, 1]. Positions decided on bar *t*'s close are
172
+ executed at bar *t+1*'s open and pay half the bar's spread per unit of position change (+ `commission_pips`).
173
+ Returned metrics per path: `total_return, max_drawdown, sharpe, n_trades, cost_paid`.
174
+ **Null paths** keep volatility clustering, tails, spreads and bar shapes but give every bar and every gap an
175
+ independent random sign, so trends, momentum, mean reversion and drift cannot exist.
176
+ `forexgen.backtest(df, positions, pip, ...)` is the same engine for your own paths.
177
+ Costs: `slippage(df, market, order_type, model)`, `stop_fill(...)` (a stop that gaps fills at the gap, never at
178
+ the stop price), `swap_pips(...)` (17:00 NY rollover, Wednesday triple).
179
+
180
+ ### Calibration and profiles
181
+ ```python
182
+ profile = forexgen.calibrate("DAT_MT_GBPUSD_M1_202608.csv", market="GBPUSD", tz="America/New_York")
183
+ forexgen.use_profile(profile) # or register_market(..., profile=profile)
184
+ profile.save("gbpusd.json"); forexgen.load_profile("gbpusd.json")
185
+ real = forexgen.read_histdata(path) # UTC M1 DataFrame
186
+ forexgen.scorecard(forexgen.get_data("GBPUSD", "M1", "6m", profile="calm_aug2026"), "GBPUSD", reference=real)
187
+ ```
188
+ `calibrate` reads the HistData/MT layout (no header: `YYYY.MM.DD,HH:MM,o,h,l,c,v`), converts the **New York
189
+ clock (DST-aware)** to UTC, and fits: volatility level, event-free seasonality, microstructure (lag-1 bounce and
190
+ variance ratios), GARCH persistence (simulated method of moments), bar-range scale, weekend gap, flat-bar and
191
+ missing-minute rates, and event sizes **only when >= 8 events of a class exist**. It cannot fit spreads or volume
192
+ from a price-only file (kept as "assumed").
193
+
194
+ ## New in 2.1
195
+
196
+ ### Is the generator itself fair? `selftest()`
197
+ ```python
198
+ report = forexgen.selftest("EURUSD", profile="typical") # ~30 s
199
+ print(report.passed, report.warnings)
200
+ ```
201
+ Checks market-like statistics on M1 paths (variance ratios - **a trend-biased generator has VR > 1**, lag-1
202
+ autocorrelation, volatility clustering, fat tails, OHLC validity) and a **free-edge test**: a plain trend strategy and a plain
203
+ mean-reversion strategy run on ordinary paths and on luck-only paths; if ordinary paths pay either one significantly more
204
+ than luck (z > 3.5) the generator is leaking an edge and the check FAILS. It also works as a guard for your own
205
+ `profile=` / `calibrate()` results: `forexgen.selftest("GBPUSD", profile=my_profile)`. CLI: `python -m forexgen --selftest`.
206
+
207
+ ### One-page verdict for your strategy: `robustness_report(strategy, ...)`
208
+ ```python
209
+ rep = forexgen.robustness_report(my_strategy, "EURUSD", "H1", "3m", n=30, commission_pips=0.3, real_result=0.04)
210
+ rep.tables["costs"], rep.tables["stress"], rep.passed
211
+ ```
212
+ Runs the strategy on regular paths, luck-only paths, planted momentum and mean-reversion edges, ten stress scenarios and a
213
+ cost sweep (free / 1x / 2x / 3x spread), then prints a verdict: where your real result ranks among luck-only paths, whether the
214
+ strategy can exploit a known edge, whether it is cost-fragile, and which scenarios hurt far more than normal conditions.
215
+ It cannot prove the strategy works live.
216
+
217
+ ### Indicator edge-case fixtures
218
+ ```python
219
+ from forexgen import fixtures
220
+ report = forexgen.indicator_stress_test(my_rsi, timeframe="M5", bounds=(0, 100)) # one row per fixture
221
+ fixtures.fixture("flat", "M15", 500) # deterministic data
222
+ ```
223
+ 16 reproducible fixtures (`fixtures.FIXTURES`): flat, one-way trends, staircase, saw-tooth, huge gap, spike-and-reverse,
224
+ zero-range bars, volatility burst, missing bars, tiny/huge prices, one/two bars, NaN inside the input. The tester reports
225
+ exceptions, NaN **after** warm-up, inf, wrong output length and out-of-bounds values.
226
+
227
+ ### Daily bars at the New York close
228
+ `get_data(..., "D1", daily_anchor="ny")` (and `W1`) cuts bars at **17:00 New York** (Sunday's session belongs to Monday's bar;
229
+ 5 bars per week; the timestamp is the bar's open in UTC, 21:00 or 22:00). Default stays `"utc"` (as in v1). CLI `--daily-anchor ny`.
230
+
231
+ ### HistData / MetaTrader writers
232
+ ```python
233
+ forexgen.generate_histdata("EURUSD", "3m", folder="out", seed=7) # DAT_MT_EURUSD_M1_202401.csv ... per New York month
234
+ forexgen.export_histdata(df_m1, "out/x.csv", "EURUSD", fmt="mt") # or fmt="ascii" (HistData ASCII, fixed EST)
235
+ ```
236
+ `fmt="mt"`: `YYYY.MM.DD,HH:MM,o,h,l,c,v`, New York local time with DST (same as the real Aug-2026 file). `fmt="ascii"`:
237
+ `YYYYMMDD HHMMSS;o;h;l;c;v`, fixed EST (UTC-5). Prices have digits+1 decimals; `volume="zero"` mimics the real MT files. Files
238
+ read back exactly with `read_histdata` (both layouts are auto-detected, including across the November DST change). CLI: `--histdata`.
239
+
240
+ ### Calibrating many months / markets
241
+ ```python
242
+ cs = forexgen.calibrate_many(glob.glob("real/DAT_MT_*_M1_*.csv")) # symbol read from the file name, or {symbol: paths}
243
+ cs.apply(); cs.save("my_calibration"); forexgen.CalibrationSet.load("my_calibration")
244
+ forexgen.set_correlation(cs.corr); forexgen.reset_correlation()
245
+ ```
246
+ Months of one market are pooled into ONE profile (pooled level, seasonality, microstructure; **no regime fitting**); the
247
+ variance level is calm/typical automatically. The correlation matrix is measured from M15 returns of the base markets over
248
+ their common period and installed by `apply()` (`set_correlation` scales your desired matrix by the attenuation found when
249
+ tuning the defaults, so realised correlations follow it). Derived crosses are skipped (they follow their legs).
250
+
251
+ ### Tested environments
252
+ Full test-suite (see REPORT.md) on **pandas 2.2.3 / numpy 1.26.4** and **pandas 3.0.2 / numpy 2.4.4**, Python 3.12.
253
+ Older pandas/numpy are untested.
254
+
255
+ ## Markets: what is calibrated and what is assumed
256
+
257
+ | | EURUSD | all other markets |
258
+ |---|---|---|
259
+ | calm volatility level (4.4%) | **calibrated** (Aug-2026) | assumed (0.63 x long-run) |
260
+ | long-run volatility level | assumed (7%) | assumed (GBPUSD 8.5, AUDUSD 10, NZDUSD 10.5, USDJPY 9.5, USDCHF 8, USDCAD 6.5, XAUUSD 16 %) |
261
+ | hourly (NY) seasonality | **calibrated** (events excluded) | assumed (EURUSD curve reshaped for each market's sessions) |
262
+ | microstructure (bounce, variance ratios), GARCH, bar range, weekend gap, flat bars, missing minutes | **calibrated** | assumed (copied from EURUSD) |
263
+ | event shock sizes | assumed, sanity-checked against 8 observed events | assumed (scaled by volatility ratio) |
264
+ | spreads, volume | assumed (price-only file) | assumed |
265
+ | regimes, scenarios, correlations | assumed | assumed (latent matrix tuned so realised M15 correlations land near typical values) |
266
+ | EURGBP, EURJPY, GBPJPY | derived exactly from their legs (no separate parameters) | |
267
+
268
+ `forexgen.describe("GBPUSD")` prints this per market. Upgrade any market with `calibrate()` on a real file.
269
+
270
+ ## Compatibility notes (changes from v1.0.0)
271
+
272
+ Kept: every function name and signature, the CSV layout and timestamp format, the CLI, durations/timeframes, the
273
+ 11 markets, `register_market`, the v1 model options, and "same seed -> same first N days whatever the duration".
274
+ Changed on purpose:
275
+ * **`seed` now defaults to `None`** (v1: 42): data differs every call. Pass an integer to get v1-style repeatability.
276
+ * **Market hours** are Sunday 17:00 -> Friday 17:00 **New York time, DST-aware** (v1: fixed 22:00 UTC).
277
+ * The old random news jumps are replaced by the scheduled-event calendar plus a small residual shock process.
278
+ * Default start prices are unchanged (e.g. EURUSD 1.0850); the calibrated *behaviour* comes from the Aug-2026 data.
279
+ * Extra columns only appear when asked for (`return_labels=True`).
280
+ * Daily bars are still cut at **UTC midnight** by default (as in v1); use `daily_anchor="ny"` for the 17:00 New York close.
281
+
282
+ ## Reproducibility rules
283
+ An explicit `seed` gives identical output for identical arguments. Every random stream is keyed by
284
+ (seed, stream, day-block) - and shared factors by absolute UTC time - so a 60-day request starts with exactly the
285
+ same 30 days as a 30-day request, and a market alone equals the same market inside `get_multi`.
286
+ Scenario positions given as a *fraction* depend on the duration; give a timestamp to keep prefix-stability.
287
+
288
+ ## Limitations
289
+ * One real month of one market is the only real-data anchor. Non-EURUSD markets are plausible, not fitted;
290
+ `calibrate_many` has only been tested on synthetic multi-market files, never on real multi-month data.
291
+ * A calm month cannot teach crashes or regime history; `stressed`, regimes and scenarios are assumptions.
292
+ * Event dates are rule-based approximations; event sizes for importance classes are assumptions.
293
+ * Realised cross-market correlations are close to, not exactly, the targets (EURUSD/USDCHF reaches about -0.83
294
+ against a -0.90 target); `scorecard`/your own checks report actuals.
295
+ * Scheduled-event shock sizes use an uncapped lognormal, so the extreme tail is probably too fat: in the typical profile a
296
+ single 3-month path can contain a 40-95 bp one-minute move on an important release (real EURUSD rarely exceeds ~50 bp).
297
+ * The model has no central-bank regime shifts, trending macro cycles, or order-book dynamics.
298
+ * Spread and volume are models, not broker data. Tick generation is slow.
299
+
300
+ ## Overfitting warning
301
+ Tuning a strategy until it looks good on synthetic data (or on a handful of paths) proves nothing. Always check a
302
+ result against `monte_carlo(mode="null")`, keep out-of-sample paths you never tuned on, include costs, and validate
303
+ on **real** data and a forward test before risking money.
@@ -0,0 +1,267 @@
1
+ # forexgen 2.1
2
+
3
+ Offline synthetic **forex and gold** data generator for **indicator testing and strategy backtesting**
4
+ (and, secondarily, producing training data). Pure Python, **numpy + pandas only**, no internet, no GUI.
5
+
6
+ * 11 markets: EURUSD GBPUSD USDJPY AUDUSD NZDUSD USDCHF USDCAD EURGBP EURJPY GBPJPY XAUUSD (+ your own).
7
+ * **Different data on every run by default**; the seed used is recorded so any run can be reproduced.
8
+ * Real market structure: volatility clustering, fat tails, DST-aware sessions, scheduled news (NFP, CPI,
9
+ FOMC, ECB, BoE ...), regimes, weekend gaps, spreads that widen at news/rollover, intrabar ticks with bid/ask.
10
+ * Several markets simulated **jointly** (shared factors, correlations that rise in crises); crosses derived
11
+ **exactly** from their legs.
12
+ * Backtest-validation tools: luck-only ("null") paths, planted-edge paths, Monte Carlo of your strategy.
13
+ * `calibrate()` fits a market to a real HistData/MetaTrader M1 file; `calibrate_many()` does it for several markets and
14
+ months at once and measures their correlations; `scorecard()` compares synthetic with real.
15
+ * **New in 2.1:** `selftest()` (is the generator itself giving strategies a free edge?), `robustness_report(strategy)`
16
+ (one-page verdict), indicator edge-case `fixtures` + `indicator_stress_test`, daily bars cut at the **17:00 New York
17
+ close** (`daily_anchor="ny"`), **HistData/MetaTrader file writers** (`export_histdata`, `generate_histdata`),
18
+ correlation control (`set_correlation`), and shared microstructure noise so tightly coupled pairs (EURUSD/USDCHF)
19
+ correlate more like real markets.
20
+
21
+ > **Read this first.** Only **EURUSD** was calibrated to real data (one real month, Aug 2026, 28,643 M1 bars).
22
+ > Every other market is built from **documented assumptions** about its typical behaviour, scaled from the
23
+ > EURUSD baseline, and says so in `forexgen.describe("GBPUSD")`. Synthetic data can test your **logic,
24
+ > robustness and cost sensitivity**; it **cannot prove a strategy makes money live** (no real news,
25
+ > central-bank behaviour or regime history). See [Limitations](#limitations).
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ pip install forexgen
31
+ python -m pytest tests # optional
32
+ ```
33
+
34
+ ## Quick start
35
+
36
+ ```python
37
+ import forexgen
38
+
39
+ df = forexgen.get_data("EURUSD", "M15", "3m") # different every call
40
+ print(df.attrs["seed"]) # ... but reproducible
41
+ df = forexgen.get_data("EURUSD", "M15", "3m", seed=df.attrs["seed"])
42
+
43
+ forexgen.generate_csv("GBPUSD", "H1", "1y", seed=7, folder="data") # data/GBPUSD_H1_1y_seed7.csv
44
+ data = forexgen.get_multi(["EURUSD", "GBPUSD", "XAUUSD", "EURGBP"], "M15", "1y", seed=1)
45
+ forexgen.describe("XAUUSD") # calibrated vs assumed
46
+ ```
47
+
48
+ Command line: `python -m forexgen EURUSD M15 3m --seed 7 --out data` (`--list`, `--profile`, `--scenario`,
49
+ `--sparse`, `--labels`, `--ticks`, `--multi EURUSD,GBPUSD`, `--describe`, `--calibrate FILE`). The seed used is printed.
50
+
51
+ ## Output format (unchanged from v1)
52
+
53
+ `time` index/column = **naive UTC**, format `%Y-%m-%d %H:%M:%S`; columns `open, high, low, close, volume, spread_pips`
54
+ (`volume` = tick volume, int). Prices are bid-style OHLC rounded to the market's digits. All timeframes are
55
+ aggregated from the same M1 path, so they agree.
56
+
57
+ ## API
58
+
59
+ ### `get_data(market, timeframe="M15", duration="30d", start="2024-01-01 00:00", end=None, seed=None, start_price=None, **options)`
60
+
61
+ | argument | meaning |
62
+ |---|---|
63
+ | `market` | e.g. `"EURUSD"`, `"XAUUSD"` (case/slash-insensitive: `"eur/usd"`). `list_markets()` |
64
+ | `timeframe` | `M1 M5 M15 M30 H1 H4 D1 W1` (any `M<n>`/`H<n>`; also `15m`, `1h`, `daily`) |
65
+ | `duration` | `30d`, `2w`, `3m`, `1y`, `12h`, `90min` (**market time**: 1w = 5 days, 1m = 21 days, 1y = 260 days) |
66
+ | `start`, `end` | naive UTC timestamps; `end` overrides `duration` |
67
+ | `seed` | `None` (default): fresh data every call. An integer reproduces the data exactly. Stored in `df.attrs["seed"]` |
68
+ | `start_price` | override the first price (for crosses it adjusts the first leg) |
69
+ | `profile` | `"typical"` (default), `"calm_aug2026"` (the real calm month's level), `"stressed"`, or a `Profile` |
70
+ | `scenario`, `scenario_at`, `scenario_strength`, `scenario_direction`, `scenario_markets` | inject a stress scenario (below) |
71
+ | `events` | `"default"` (rule calendar), `"none"`, or a DataFrame / CSV of your own events |
72
+ | `sparse` | `True`: broker-like flat bars and missing minutes (measured EURUSD rates) |
73
+ | `return_labels` | adds `regime`, `event_flag`, `true_vol` columns |
74
+ | `edge` | `dict(kind="momentum"\|"mean_reversion", strength=0.02, horizon="1h")` plants a known edge |
75
+ | `**model` | v1 options still accepted: `garch=(a,b)`, `t_df`, `jump_rate_per_day`, `jump_sigma`, `weekend_gap`, `kappa`, `drift`, `usd_factor`; plus `vol_scale` |
76
+
77
+ How the v1 options map: `garch` sets the GARCH alpha/beta; `t_df` the Student-t tail; `jump_rate_per_day` /
78
+ `jump_sigma` the **residual unscheduled** shock process (scheduled news now comes from the calendar);
79
+ `weekend_gap` is the weekend-gap standard deviation in per-minute sigmas; `kappa` the mean-reversion speed;
80
+ `drift` an annual log drift; `usd_factor=False` removes the cross-market factor.
81
+
82
+ ### `generate_csv(market, timeframe="M15", duration="30d", path=None, folder="forex_data", **kwargs)`
83
+ Writes the CSV and returns its path. Default name `<folder>/<SYMBOL>_<TF>_<duration>_seed<seed>.csv`
84
+ (`..._to-<digits of end>_...` when `end` is given). With `seed=None` the seed actually used is in the name.
85
+
86
+ ### `get_multi(markets, timeframe, duration, start, end, seed, profile, as_frame=False, **options)`
87
+ Joint simulation. Returns `{symbol: DataFrame}` (or one MultiIndex-column DataFrame). **A market inside
88
+ `get_multi` equals `get_data(market, seed=seed)`** for the same arguments. Crosses are derived exactly:
89
+ `EURGBP = EURUSD / GBPUSD`, `EURJPY = EURUSD * USDJPY`, `GBPJPY = GBPUSD * USDJPY`.
90
+
91
+ ### `get_ticks(market, duration="1d", start, end, seed, profile, ticks_per_minute="auto", ...)`
92
+ Bid/ask ticks inside every M1 bar on a Brownian-bridge path **conditioned on the bar's OHLC** (it visits the
93
+ high and low in a defined order), so stop/target order inside a bar is decided by a path, not a guess. Bars
94
+ rebuilt from the bid ticks equal `get_data(..., "M1")` for the same seed. ~100 µs per minute bar (off by default).
95
+
96
+ ### Events: `get_events`, `register_event`, `set_events`
97
+ `get_events("EURUSD", start, end)` returns the calendar used for that market (with its sensitivity).
98
+ `register_event(name, time_utc, importance=3, currencies=("USD",))` adds one event; `set_events(df_or_csv)`
99
+ replaces the rule calendar (columns `time_utc, name, importance, currencies`); `set_events(None)` restores it.
100
+ **All default dates are rule-based approximations, not the official calendar:**
101
+
102
+ | event | rule (local time) | importance |
103
+ |---|---|---|
104
+ | US NFP | first Friday 08:30 NY | 5 |
105
+ | US CPI | Wednesday in 11th-17th, 08:30 NY | 4 |
106
+ | US FOMC | 8/yr, Wednesday 14:00 NY | 5 |
107
+ | US retail sales / PCE / ISM / UMich | 08:30 or 10:00 NY | 2-3 |
108
+ | Jackson Hole | Friday in 22nd-28th of August, 10:00 NY | 4 |
109
+ | ECB | 8/yr, Thursday 14:15 Frankfurt | 4 |
110
+ | BoE | 8/yr, Thursday 12:00 London | 4 |
111
+ | BoJ, RBA, RBNZ, SNB, BoC | 3-8/yr, local release times | 3 |
112
+ | US jobless claims | every Thursday 08:30 NY | 1 |
113
+
114
+ Reaction (assumed shape; sizes anchored to the 8 events observed in the real file): vol x0.75 in the 30 minutes
115
+ before, a random-sign heavy-tailed shock (80/12/5/3% over the first 4 minutes), power-law volatility decay for
116
+ ~4 hours, spread widening. USD events move every USD pair (opposite sign for USDxxx vs xxxUSD, x1.5 for gold);
117
+ other currencies' events move the pairs that contain them.
118
+
119
+ ### Scenarios
120
+ `scenario=` one of `flash_crash, peg_break, rate_shock, trend_up, trend_down, range, high_vol, low_liquidity,
121
+ gap, intervention, crisis`, placed at `scenario_at` (timestamp, or a fraction 0..1 of the data) and scaled by
122
+ `scenario_strength`. They are **parameter transformations of the base model** (jumps, volatility/spread
123
+ multipliers, drift, mean reversion, forced global stress), documented in `forexgen/scenarios.py`.
124
+ `crisis` also forces the global stress state, so correlations rise toward 1 in `get_multi`.
125
+ `scenario_markets=[...]` restricts which markets get the scenario; `scenario_direction="up"|"down"|"auto"`.
126
+
127
+ ### Backtest validation tools
128
+ ```python
129
+ null = forexgen.monte_carlo(strategy, "EURUSD", "M5", "1m", n=200, mode="null") # luck only
130
+ edge = forexgen.monte_carlo(strategy, "EURUSD", "M5", "1m", n=50, mode="edge") # planted edge
131
+ reg = forexgen.monte_carlo(strategy, "EURUSD", "M5", "1m", n=50, mode="regular")
132
+ forexgen.percentile_in_null(my_real_backtest_return, null) # >95 = unlikely luck
133
+ paths = forexgen.get_null_paths("EURUSD", "H1", "3m", n=100) # the raw null paths
134
+ ```
135
+ `strategy(df) -> pandas.Series` of target positions in [-1, 1]. Positions decided on bar *t*'s close are
136
+ executed at bar *t+1*'s open and pay half the bar's spread per unit of position change (+ `commission_pips`).
137
+ Returned metrics per path: `total_return, max_drawdown, sharpe, n_trades, cost_paid`.
138
+ **Null paths** keep volatility clustering, tails, spreads and bar shapes but give every bar and every gap an
139
+ independent random sign, so trends, momentum, mean reversion and drift cannot exist.
140
+ `forexgen.backtest(df, positions, pip, ...)` is the same engine for your own paths.
141
+ Costs: `slippage(df, market, order_type, model)`, `stop_fill(...)` (a stop that gaps fills at the gap, never at
142
+ the stop price), `swap_pips(...)` (17:00 NY rollover, Wednesday triple).
143
+
144
+ ### Calibration and profiles
145
+ ```python
146
+ profile = forexgen.calibrate("DAT_MT_GBPUSD_M1_202608.csv", market="GBPUSD", tz="America/New_York")
147
+ forexgen.use_profile(profile) # or register_market(..., profile=profile)
148
+ profile.save("gbpusd.json"); forexgen.load_profile("gbpusd.json")
149
+ real = forexgen.read_histdata(path) # UTC M1 DataFrame
150
+ forexgen.scorecard(forexgen.get_data("GBPUSD", "M1", "6m", profile="calm_aug2026"), "GBPUSD", reference=real)
151
+ ```
152
+ `calibrate` reads the HistData/MT layout (no header: `YYYY.MM.DD,HH:MM,o,h,l,c,v`), converts the **New York
153
+ clock (DST-aware)** to UTC, and fits: volatility level, event-free seasonality, microstructure (lag-1 bounce and
154
+ variance ratios), GARCH persistence (simulated method of moments), bar-range scale, weekend gap, flat-bar and
155
+ missing-minute rates, and event sizes **only when >= 8 events of a class exist**. It cannot fit spreads or volume
156
+ from a price-only file (kept as "assumed").
157
+
158
+ ## New in 2.1
159
+
160
+ ### Is the generator itself fair? `selftest()`
161
+ ```python
162
+ report = forexgen.selftest("EURUSD", profile="typical") # ~30 s
163
+ print(report.passed, report.warnings)
164
+ ```
165
+ Checks market-like statistics on M1 paths (variance ratios - **a trend-biased generator has VR > 1**, lag-1
166
+ autocorrelation, volatility clustering, fat tails, OHLC validity) and a **free-edge test**: a plain trend strategy and a plain
167
+ mean-reversion strategy run on ordinary paths and on luck-only paths; if ordinary paths pay either one significantly more
168
+ than luck (z > 3.5) the generator is leaking an edge and the check FAILS. It also works as a guard for your own
169
+ `profile=` / `calibrate()` results: `forexgen.selftest("GBPUSD", profile=my_profile)`. CLI: `python -m forexgen --selftest`.
170
+
171
+ ### One-page verdict for your strategy: `robustness_report(strategy, ...)`
172
+ ```python
173
+ rep = forexgen.robustness_report(my_strategy, "EURUSD", "H1", "3m", n=30, commission_pips=0.3, real_result=0.04)
174
+ rep.tables["costs"], rep.tables["stress"], rep.passed
175
+ ```
176
+ Runs the strategy on regular paths, luck-only paths, planted momentum and mean-reversion edges, ten stress scenarios and a
177
+ cost sweep (free / 1x / 2x / 3x spread), then prints a verdict: where your real result ranks among luck-only paths, whether the
178
+ strategy can exploit a known edge, whether it is cost-fragile, and which scenarios hurt far more than normal conditions.
179
+ It cannot prove the strategy works live.
180
+
181
+ ### Indicator edge-case fixtures
182
+ ```python
183
+ from forexgen import fixtures
184
+ report = forexgen.indicator_stress_test(my_rsi, timeframe="M5", bounds=(0, 100)) # one row per fixture
185
+ fixtures.fixture("flat", "M15", 500) # deterministic data
186
+ ```
187
+ 16 reproducible fixtures (`fixtures.FIXTURES`): flat, one-way trends, staircase, saw-tooth, huge gap, spike-and-reverse,
188
+ zero-range bars, volatility burst, missing bars, tiny/huge prices, one/two bars, NaN inside the input. The tester reports
189
+ exceptions, NaN **after** warm-up, inf, wrong output length and out-of-bounds values.
190
+
191
+ ### Daily bars at the New York close
192
+ `get_data(..., "D1", daily_anchor="ny")` (and `W1`) cuts bars at **17:00 New York** (Sunday's session belongs to Monday's bar;
193
+ 5 bars per week; the timestamp is the bar's open in UTC, 21:00 or 22:00). Default stays `"utc"` (as in v1). CLI `--daily-anchor ny`.
194
+
195
+ ### HistData / MetaTrader writers
196
+ ```python
197
+ forexgen.generate_histdata("EURUSD", "3m", folder="out", seed=7) # DAT_MT_EURUSD_M1_202401.csv ... per New York month
198
+ forexgen.export_histdata(df_m1, "out/x.csv", "EURUSD", fmt="mt") # or fmt="ascii" (HistData ASCII, fixed EST)
199
+ ```
200
+ `fmt="mt"`: `YYYY.MM.DD,HH:MM,o,h,l,c,v`, New York local time with DST (same as the real Aug-2026 file). `fmt="ascii"`:
201
+ `YYYYMMDD HHMMSS;o;h;l;c;v`, fixed EST (UTC-5). Prices have digits+1 decimals; `volume="zero"` mimics the real MT files. Files
202
+ read back exactly with `read_histdata` (both layouts are auto-detected, including across the November DST change). CLI: `--histdata`.
203
+
204
+ ### Calibrating many months / markets
205
+ ```python
206
+ cs = forexgen.calibrate_many(glob.glob("real/DAT_MT_*_M1_*.csv")) # symbol read from the file name, or {symbol: paths}
207
+ cs.apply(); cs.save("my_calibration"); forexgen.CalibrationSet.load("my_calibration")
208
+ forexgen.set_correlation(cs.corr); forexgen.reset_correlation()
209
+ ```
210
+ Months of one market are pooled into ONE profile (pooled level, seasonality, microstructure; **no regime fitting**); the
211
+ variance level is calm/typical automatically. The correlation matrix is measured from M15 returns of the base markets over
212
+ their common period and installed by `apply()` (`set_correlation` scales your desired matrix by the attenuation found when
213
+ tuning the defaults, so realised correlations follow it). Derived crosses are skipped (they follow their legs).
214
+
215
+ ### Tested environments
216
+ Full test-suite (see REPORT.md) on **pandas 2.2.3 / numpy 1.26.4** and **pandas 3.0.2 / numpy 2.4.4**, Python 3.12.
217
+ Older pandas/numpy are untested.
218
+
219
+ ## Markets: what is calibrated and what is assumed
220
+
221
+ | | EURUSD | all other markets |
222
+ |---|---|---|
223
+ | calm volatility level (4.4%) | **calibrated** (Aug-2026) | assumed (0.63 x long-run) |
224
+ | long-run volatility level | assumed (7%) | assumed (GBPUSD 8.5, AUDUSD 10, NZDUSD 10.5, USDJPY 9.5, USDCHF 8, USDCAD 6.5, XAUUSD 16 %) |
225
+ | hourly (NY) seasonality | **calibrated** (events excluded) | assumed (EURUSD curve reshaped for each market's sessions) |
226
+ | microstructure (bounce, variance ratios), GARCH, bar range, weekend gap, flat bars, missing minutes | **calibrated** | assumed (copied from EURUSD) |
227
+ | event shock sizes | assumed, sanity-checked against 8 observed events | assumed (scaled by volatility ratio) |
228
+ | spreads, volume | assumed (price-only file) | assumed |
229
+ | regimes, scenarios, correlations | assumed | assumed (latent matrix tuned so realised M15 correlations land near typical values) |
230
+ | EURGBP, EURJPY, GBPJPY | derived exactly from their legs (no separate parameters) | |
231
+
232
+ `forexgen.describe("GBPUSD")` prints this per market. Upgrade any market with `calibrate()` on a real file.
233
+
234
+ ## Compatibility notes (changes from v1.0.0)
235
+
236
+ Kept: every function name and signature, the CSV layout and timestamp format, the CLI, durations/timeframes, the
237
+ 11 markets, `register_market`, the v1 model options, and "same seed -> same first N days whatever the duration".
238
+ Changed on purpose:
239
+ * **`seed` now defaults to `None`** (v1: 42): data differs every call. Pass an integer to get v1-style repeatability.
240
+ * **Market hours** are Sunday 17:00 -> Friday 17:00 **New York time, DST-aware** (v1: fixed 22:00 UTC).
241
+ * The old random news jumps are replaced by the scheduled-event calendar plus a small residual shock process.
242
+ * Default start prices are unchanged (e.g. EURUSD 1.0850); the calibrated *behaviour* comes from the Aug-2026 data.
243
+ * Extra columns only appear when asked for (`return_labels=True`).
244
+ * Daily bars are still cut at **UTC midnight** by default (as in v1); use `daily_anchor="ny"` for the 17:00 New York close.
245
+
246
+ ## Reproducibility rules
247
+ An explicit `seed` gives identical output for identical arguments. Every random stream is keyed by
248
+ (seed, stream, day-block) - and shared factors by absolute UTC time - so a 60-day request starts with exactly the
249
+ same 30 days as a 30-day request, and a market alone equals the same market inside `get_multi`.
250
+ Scenario positions given as a *fraction* depend on the duration; give a timestamp to keep prefix-stability.
251
+
252
+ ## Limitations
253
+ * One real month of one market is the only real-data anchor. Non-EURUSD markets are plausible, not fitted;
254
+ `calibrate_many` has only been tested on synthetic multi-market files, never on real multi-month data.
255
+ * A calm month cannot teach crashes or regime history; `stressed`, regimes and scenarios are assumptions.
256
+ * Event dates are rule-based approximations; event sizes for importance classes are assumptions.
257
+ * Realised cross-market correlations are close to, not exactly, the targets (EURUSD/USDCHF reaches about -0.83
258
+ against a -0.90 target); `scorecard`/your own checks report actuals.
259
+ * Scheduled-event shock sizes use an uncapped lognormal, so the extreme tail is probably too fat: in the typical profile a
260
+ single 3-month path can contain a 40-95 bp one-minute move on an important release (real EURUSD rarely exceeds ~50 bp).
261
+ * The model has no central-bank regime shifts, trending macro cycles, or order-book dynamics.
262
+ * Spread and volume are models, not broker data. Tick generation is slow.
263
+
264
+ ## Overfitting warning
265
+ Tuning a strategy until it looks good on synthetic data (or on a handful of paths) proves nothing. Always check a
266
+ result against `monte_carlo(mode="null")`, keep out-of-sample paths you never tuned on, include costs, and validate
267
+ on **real** data and a forward test before risking money.
@@ -0,0 +1,27 @@
1
+ """forexgen 2.0 - offline synthetic forex / gold data generator.
2
+
3
+ Quick start:
4
+ import forexgen
5
+ df = forexgen.get_data("EURUSD", "M15", "3m") # different every call; seed in df.attrs
6
+ df = forexgen.get_data("GBPUSD", "H1", "1y", seed=7) # reproducible
7
+ forexgen.describe("XAUUSD") # calibrated vs assumed parameters
8
+ """
9
+ from .api import (generate_csv, get_data, get_multi, list_markets, parse_duration, # noqa: F401
10
+ parse_timeframe, register_market)
11
+ from .profiles import (Profile, describe, get_profile, load_profile, save_profile, # noqa: F401
12
+ use_profile, reset_profiles)
13
+ from .events import get_events, register_event, set_events, clear_user_events # noqa: F401
14
+ from .scenarios import SCENARIOS # noqa: F401
15
+ from .ticks import get_ticks # noqa: F401
16
+ from .report import scorecard, stats_m1 # noqa: F401
17
+ from .calibration import calibrate, calibrate_many, CalibrationSet, read_histdata # noqa: F401
18
+ from .validation import get_null_paths, monte_carlo, percentile_in_null, backtest # noqa: F401
19
+ from .costs import slippage, stop_fill, swap_pips # noqa: F401
20
+
21
+ from .factors import set_correlation, reset_correlation # noqa: F401
22
+ from .export import export_histdata, generate_histdata, to_histdata_frame # noqa: F401
23
+ from .diagnostics import selftest, robustness_report, trend_strategy, mean_reversion_strategy # noqa: F401
24
+ from . import fixtures # noqa: F401
25
+ from .fixtures import indicator_stress_test # noqa: F401
26
+
27
+ __version__ = "2.1.0"
@@ -0,0 +1,5 @@
1
+ import sys
2
+
3
+ from .cli import main
4
+
5
+ sys.exit(main())