backtestchat 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,303 @@
1
+ """Long/flat signal functions.
2
+
3
+ Each strategy has a `*_series(bars, params) -> (signals, indicators)` function:
4
+ `signals` is the per-bar target position (1 = fully long, 0 = flat) and
5
+ `indicators` is an ordered dict of the named derived series the trade rule
6
+ reads (per-bar lists aligned 1:1 with bars, None during warmup). The
7
+ `*_signals(bars, params) -> list[int]` wrappers keep the original engine
8
+ contract. `bars` is a list of dicts with keys ts/open/high/low/close/volume.
9
+ Warmup bars (before the indicator is defined) are flat (0). Stateful signals
10
+ (rsi/bollinger/donchian/atr_channel) hold their position between an entry and
11
+ exit condition.
12
+
13
+ Indicator keys are parameterized (e.g. `sma_50`, `bb_lower_20_2`) so consumers
14
+ joining series from multiple strategies never mix differently-parameterized
15
+ series under one name.
16
+ """
17
+
18
+ from backtestchat.strategy import indicators as ind
19
+
20
+
21
+ def _field(bars, key):
22
+ return [float(b[key]) for b in bars]
23
+
24
+
25
+ def _fmt(value):
26
+ """Format a numeric param for an indicator key (2.0 -> "2")."""
27
+ return f"{value:g}"
28
+
29
+
30
+ def buy_hold_series(bars, params):
31
+ return [1] * len(bars), {}
32
+
33
+
34
+ def buy_hold_signals(bars, params):
35
+ return buy_hold_series(bars, params)[0]
36
+
37
+
38
+ def ma_crossover_series(bars, params):
39
+ closes = _field(bars, "close")
40
+ fast = ind.sma(closes, params["fast"])
41
+ slow = ind.sma(closes, params["slow"])
42
+ indicators = {
43
+ f"sma_{params['fast']}": fast,
44
+ f"sma_{params['slow']}": slow,
45
+ }
46
+ signals = [
47
+ 1 if (fast[i] is not None and slow[i] is not None and fast[i] > slow[i]) else 0
48
+ for i in range(len(closes))
49
+ ]
50
+ return signals, indicators
51
+
52
+
53
+ def ma_crossover_signals(bars, params):
54
+ return ma_crossover_series(bars, params)[0]
55
+
56
+
57
+ def ema_crossover_series(bars, params):
58
+ closes = _field(bars, "close")
59
+ fast = ind.ema(closes, params["fast"])
60
+ slow = ind.ema(closes, params["slow"])
61
+ indicators = {
62
+ f"ema_{params['fast']}": fast,
63
+ f"ema_{params['slow']}": slow,
64
+ }
65
+ signals = [
66
+ 1 if (fast[i] is not None and slow[i] is not None and fast[i] > slow[i]) else 0
67
+ for i in range(len(closes))
68
+ ]
69
+ return signals, indicators
70
+
71
+
72
+ def ema_crossover_signals(bars, params):
73
+ return ema_crossover_series(bars, params)[0]
74
+
75
+
76
+ def macd_series(bars, params):
77
+ closes = _field(bars, "close")
78
+ macd_line, signal_line = ind.macd(closes, params["fast"], params["slow"], params["signal"])
79
+ indicators = {
80
+ f"macd_{params['fast']}_{params['slow']}": macd_line,
81
+ f"macd_signal_{params['fast']}_{params['slow']}_{params['signal']}": signal_line,
82
+ }
83
+ signals = [
84
+ 1 if (macd_line[i] is not None and signal_line[i] is not None and macd_line[i] > signal_line[i]) else 0
85
+ for i in range(len(closes))
86
+ ]
87
+ return signals, indicators
88
+
89
+
90
+ def macd_signals(bars, params):
91
+ return macd_series(bars, params)[0]
92
+
93
+
94
+ def roc_series(bars, params):
95
+ closes = _field(bars, "close")
96
+ values = ind.roc(closes, params["period"])
97
+ indicators = {f"roc_{params['period']}": values}
98
+ signals = [1 if (values[i] is not None and values[i] > 0) else 0 for i in range(len(closes))]
99
+ return signals, indicators
100
+
101
+
102
+ def roc_signals(bars, params):
103
+ return roc_series(bars, params)[0]
104
+
105
+
106
+ def price_above_sma_series(bars, params):
107
+ closes = _field(bars, "close")
108
+ avg = ind.sma(closes, params["window"])
109
+ indicators = {f"sma_{params['window']}": avg}
110
+ signals = [
111
+ 1 if (avg[i] is not None and closes[i] > avg[i]) else 0
112
+ for i in range(len(closes))
113
+ ]
114
+ return signals, indicators
115
+
116
+
117
+ def price_above_sma_signals(bars, params):
118
+ return price_above_sma_series(bars, params)[0]
119
+
120
+
121
+ def rsi_series(bars, params):
122
+ """Mean-reversion: enter long below `lower`, exit above `upper` (stateful)."""
123
+ closes = _field(bars, "close")
124
+ values = ind.rsi(closes, params["period"])
125
+ lower, upper = params["lower"], params["upper"]
126
+
127
+ out = []
128
+ position = 0
129
+ for v in values:
130
+ if v is None:
131
+ out.append(0)
132
+ continue
133
+ if position == 0 and v < lower:
134
+ position = 1
135
+ elif position == 1 and v > upper:
136
+ position = 0
137
+ out.append(position)
138
+
139
+ return out, {f"rsi_{params['period']}": values}
140
+
141
+
142
+ def rsi_signals(bars, params):
143
+ return rsi_series(bars, params)[0]
144
+
145
+
146
+ def bollinger_series(bars, params):
147
+ """Mean-reversion: enter long below the lower band, exit above the middle (stateful)."""
148
+ closes = _field(bars, "close")
149
+ window, num_std = params["window"], params["num_std"]
150
+ mid = ind.sma(closes, window)
151
+ std = ind.rolling_std(closes, window)
152
+
153
+ upper_band = [
154
+ (mid[i] + num_std * std[i]) if (mid[i] is not None and std[i] is not None) else None
155
+ for i in range(len(closes))
156
+ ]
157
+ lower_band = [
158
+ (mid[i] - num_std * std[i]) if (mid[i] is not None and std[i] is not None) else None
159
+ for i in range(len(closes))
160
+ ]
161
+ indicators = {
162
+ f"bb_mid_{window}": mid,
163
+ f"bb_upper_{window}_{_fmt(num_std)}": upper_band,
164
+ f"bb_lower_{window}_{_fmt(num_std)}": lower_band,
165
+ }
166
+
167
+ out = []
168
+ position = 0
169
+ for i in range(len(closes)):
170
+ if lower_band[i] is None:
171
+ out.append(0)
172
+ continue
173
+ if position == 0 and closes[i] < lower_band[i]:
174
+ position = 1
175
+ elif position == 1 and closes[i] > mid[i]:
176
+ position = 0
177
+ out.append(position)
178
+
179
+ return out, indicators
180
+
181
+
182
+ def bollinger_signals(bars, params):
183
+ return bollinger_series(bars, params)[0]
184
+
185
+
186
+ def donchian_breakout_series(bars, params):
187
+ """Trend breakout: enter long above the prior `entry`-day high, exit below the
188
+ prior `exit`-day low (stateful). Uses prior bars to avoid look-ahead."""
189
+ highs = _field(bars, "high")
190
+ lows = _field(bars, "low")
191
+ closes = _field(bars, "close")
192
+ entry, exit_w = params["entry"], params["exit"]
193
+
194
+ prior_high = [
195
+ max(highs[i - entry:i]) if i >= entry else None
196
+ for i in range(len(closes))
197
+ ]
198
+ prior_low = [
199
+ min(lows[i - exit_w:i]) if i >= exit_w else None
200
+ for i in range(len(closes))
201
+ ]
202
+ indicators = {
203
+ f"prior_high_{entry}": prior_high,
204
+ f"prior_low_{exit_w}": prior_low,
205
+ }
206
+
207
+ out = []
208
+ position = 0
209
+ for i in range(len(closes)):
210
+ if prior_high[i] is None:
211
+ out.append(0)
212
+ continue
213
+ if position == 0 and closes[i] > prior_high[i]:
214
+ position = 1
215
+ elif position == 1 and prior_low[i] is not None and closes[i] < prior_low[i]:
216
+ position = 0
217
+ out.append(position)
218
+
219
+ return out, indicators
220
+
221
+
222
+ def donchian_breakout_signals(bars, params):
223
+ return donchian_breakout_series(bars, params)[0]
224
+
225
+
226
+ def stochastic_series(bars, params):
227
+ """Long when %K > %D."""
228
+ highs = _field(bars, "high")
229
+ lows = _field(bars, "low")
230
+ closes = _field(bars, "close")
231
+ k_period, d_period = params["k"], params["d"]
232
+
233
+ hh = ind.rolling_max(highs, k_period)
234
+ ll = ind.rolling_min(lows, k_period)
235
+
236
+ percent_k = []
237
+ for i in range(len(closes)):
238
+ if hh[i] is None or ll[i] is None or hh[i] == ll[i]:
239
+ percent_k.append(None)
240
+ else:
241
+ percent_k.append(100.0 * (closes[i] - ll[i]) / (hh[i] - ll[i]))
242
+
243
+ # %D = SMA of %K over its valid tail.
244
+ valid = [v for v in percent_k if v is not None]
245
+ d_tail = ind.sma(valid, d_period)
246
+ percent_d = [None] * len(closes)
247
+ first_valid = next((i for i, v in enumerate(percent_k) if v is not None), None)
248
+ if first_valid is not None:
249
+ for offset, value in enumerate(d_tail):
250
+ percent_d[first_valid + offset] = value
251
+
252
+ indicators = {
253
+ f"stoch_k_{k_period}": percent_k,
254
+ f"stoch_d_{k_period}_{d_period}": percent_d,
255
+ }
256
+ signals = [
257
+ 1 if (percent_k[i] is not None and percent_d[i] is not None and percent_k[i] > percent_d[i]) else 0
258
+ for i in range(len(closes))
259
+ ]
260
+ return signals, indicators
261
+
262
+
263
+ def stochastic_signals(bars, params):
264
+ return stochastic_series(bars, params)[0]
265
+
266
+
267
+ def atr_channel_series(bars, params):
268
+ """Keltner-style breakout: enter long above EMA + mult*ATR, exit below EMA (stateful)."""
269
+ highs = _field(bars, "high")
270
+ lows = _field(bars, "low")
271
+ closes = _field(bars, "close")
272
+ window, mult = params["window"], params["mult"]
273
+
274
+ mid = ind.ema(closes, window)
275
+ atr_vals = ind.atr(highs, lows, closes, window)
276
+
277
+ upper = [
278
+ (mid[i] + mult * atr_vals[i]) if (mid[i] is not None and atr_vals[i] is not None) else None
279
+ for i in range(len(closes))
280
+ ]
281
+ indicators = {
282
+ f"ema_{window}": mid,
283
+ f"atr_{window}": atr_vals,
284
+ f"atr_upper_{window}_{_fmt(mult)}": upper,
285
+ }
286
+
287
+ out = []
288
+ position = 0
289
+ for i in range(len(closes)):
290
+ if upper[i] is None:
291
+ out.append(0)
292
+ continue
293
+ if position == 0 and closes[i] > upper[i]:
294
+ position = 1
295
+ elif position == 1 and closes[i] < mid[i]:
296
+ position = 0
297
+ out.append(position)
298
+
299
+ return out, indicators
300
+
301
+
302
+ def atr_channel_signals(bars, params):
303
+ return atr_channel_series(bars, params)[0]
@@ -0,0 +1,198 @@
1
+ Metadata-Version: 2.4
2
+ Name: backtestchat
3
+ Version: 0.1.0
4
+ Summary: Local-first agentic strategy lab CLI: chat, backtest, and paper-trade long/flat strategies on keyless Binance public data.
5
+ Project-URL: Homepage, https://github.com/barisarat/backtestchat
6
+ Project-URL: Repository, https://github.com/barisarat/backtestchat
7
+ Project-URL: Issues, https://github.com/barisarat/backtestchat/issues
8
+ Author: Baris Arat
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: agent,backtest,binance,cli,langgraph,paper-trading,trading
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Office/Business :: Financial :: Investment
18
+ Classifier: Topic :: Scientific/Engineering
19
+ Requires-Python: >=3.12
20
+ Requires-Dist: httpx>=0.27.0
21
+ Requires-Dist: langchain-core>=0.3.0
22
+ Requires-Dist: langchain>=0.3.0
23
+ Requires-Dist: langgraph-checkpoint-sqlite>=2.0.0
24
+ Requires-Dist: langgraph>=0.2.60
25
+ Requires-Dist: plotly>=5.22.0
26
+ Requires-Dist: rich>=13.7.0
27
+ Requires-Dist: typer>=0.12.0
28
+ Provides-Extra: anthropic
29
+ Requires-Dist: langchain-anthropic>=0.2.0; extra == 'anthropic'
30
+ Provides-Extra: ollama
31
+ Requires-Dist: langchain-ollama>=0.2.0; extra == 'ollama'
32
+ Provides-Extra: openai
33
+ Requires-Dist: langchain-openai>=0.2.0; extra == 'openai'
34
+ Description-Content-Type: text/markdown
35
+
36
+ # backtestchat
37
+
38
+ An open-source, local-first agentic strategy lab in your terminal. Chat with a
39
+ LangGraph agent to fetch keyless Binance public market data, design and backtest
40
+ long/flat crypto strategies, and run simulated paper trades. Charts render to
41
+ self-contained plotly HTML that opens in your browser.
42
+
43
+ **Paper-only by design.** backtestchat simulates fills from closed bars. It never
44
+ places real orders and never needs exchange API keys. Market data comes from the
45
+ public, keyless Binance REST API. The only key you supply is your own model
46
+ (BYOK) key, or a local Ollama endpoint.
47
+
48
+ > backtestchat is a research and simulation tool and a portfolio/learning project.
49
+ > It is not financial advice and not a trading product. Do not use it to make
50
+ > investment decisions.
51
+
52
+ <!-- TODO: asciinema GIF of a chat session (quote -> backtest -> chart). -->
53
+ <!-- TODO: screenshot of a backtestReport chart. -->
54
+
55
+ ## Quickstart
56
+
57
+ Requires Python 3.12+ and [uv](https://docs.astral.sh/uv/).
58
+
59
+ ```bash
60
+ git clone https://github.com/barisarat/backtestchat
61
+ cd backtestchat
62
+ uv sync --extra openai # deps + the OpenAI model provider
63
+
64
+ export BACKTESTCHAT_MODEL=openai:gpt-5.4-nano
65
+ export OPENAI_API_KEY=sk-...
66
+
67
+ uv run backtestchat chat # talk to the agent
68
+ uv run backtestchat runs sync # advance paper runs to the latest closed bar
69
+ uv run backtestchat runs list # check paper run status
70
+ ```
71
+
72
+ Two things to know:
73
+
74
+ - The `backtestchat` command lives in the project venv, so either prefix every
75
+ command with `uv run`, or activate the venv once with
76
+ `source .venv/bin/activate` and call `backtestchat` directly.
77
+ - Always sync with the extra for the provider you use. A plain `uv sync`
78
+ removes extras, and `backtestchat chat` will fail until you
79
+ `uv sync --extra openai` again.
80
+
81
+ Market data works with **zero keys**; only the chat agent needs a model key.
82
+ Other providers (Anthropic, Ollama, ...) are covered under
83
+ [Model setup](#model-setup-byok).
84
+
85
+ ## What it does
86
+
87
+ - **Chat** with a tool-calling agent (LangGraph) that maps your plain-English
88
+ requests onto market-data, backtest, and paper-trade actions.
89
+ - **Backtest** ~11 single-signal long/flat strategies (SMA/EMA crossover, MACD,
90
+ RSI, Bollinger, Donchian, ROC, stochastic, ATR channel, price-vs-SMA,
91
+ buy & hold) with CAGR, Sharpe, max drawdown, win rate, and trade count.
92
+ - **Compare** several strategies at once, ranked, on one chart.
93
+ - **Paper-trade** a strategy with simulated fills. Runs advance by replaying
94
+ closed bars - no daemon, no scheduler. Any command can trigger a cheap sync.
95
+ - **Charts** are deterministic plotly HTML written to `./artifacts`. The model
96
+ never writes HTML; it only triggers the tools that build the charts.
97
+
98
+ Supported intervals: **15m, 1h, 4h, 1d**. Symbols: any Binance spot pair
99
+ (BTCUSDT, ETHUSDT, SOLUSDT, XRPUSDT, DOGEUSDT, ...).
100
+
101
+ ## Model setup (BYOK)
102
+
103
+ backtestchat talks to any provider via LangChain's `init_chat_model`, configured with
104
+ a `provider:model` string in `BACKTESTCHAT_MODEL` (or `model = "..."` in
105
+ `./backtestchat.toml`). The default setup uses OpenAI:
106
+
107
+ ```bash
108
+ uv sync --extra openai
109
+ export BACKTESTCHAT_MODEL=openai:gpt-5.4-nano
110
+ export OPENAI_API_KEY=sk-...
111
+ ```
112
+
113
+ Any provider `init_chat_model` supports works the same way: install the matching
114
+ extra (`--extra anthropic`, `--extra ollama`, ...) and set `BACKTESTCHAT_MODEL` to
115
+ that provider's `provider:model` string plus its API key (Ollama runs locally
116
+ and needs no key).
117
+
118
+ If no model is configured, `backtestchat chat` prints these instructions and exits.
119
+ Check what is resolved with `backtestchat config`.
120
+
121
+ ## Usage
122
+
123
+ ```bash
124
+ uv run backtestchat chat
125
+ ```
126
+
127
+ Then talk to it naturally:
128
+
129
+ ```
130
+ you > what's BTC trading at?
131
+ you > backtest an ma crossover 20/50 on BTCUSDT 1h over the last 3 months
132
+ you > compare that against buy and hold and rsi
133
+ you > save that strategy # asks you to confirm in the terminal
134
+ you > start a paper run with 5000 usdt # asks you to confirm
135
+ you > list my paper runs
136
+ ```
137
+
138
+ Charts open automatically in your browser and are saved under `./artifacts`.
139
+ Saving a strategy and starting a paper run pause for a y/n confirmation
140
+ (human-in-the-loop, implemented with LangGraph interrupts).
141
+
142
+ ### Commands
143
+
144
+ ```bash
145
+ backtestchat chat [--new] [--thread ID] # agent REPL (resumes the last thread by default)
146
+ backtestchat runs list [--status active] # list paper runs (stored state)
147
+ backtestchat runs sync # advance all active runs to the latest closed bar
148
+ backtestchat show [last|FILE] # open a rendered chart artifact
149
+ backtestchat config # print resolved configuration
150
+ ```
151
+
152
+ ### How paper runs work
153
+
154
+ A paper run does not backdate: the first simulated fill happens on the first bar
155
+ that **closes after** you create the run, so a fresh run shows
156
+ "waiting for first tick" until then. Advancing a run replays every closed bar
157
+ since its last processed bar, one signal evaluation per bar. Because the
158
+ already-processed-bar guard is the only cadence control, `backtestchat runs sync` is
159
+ safe to run anytime and is idempotent.
160
+
161
+ ## LangSmith tracing (optional)
162
+
163
+ Set the standard env vars and traces appear in your LangSmith project - no code
164
+ or flags required:
165
+
166
+ ```bash
167
+ export LANGSMITH_TRACING=true
168
+ export LANGSMITH_API_KEY=...
169
+ ```
170
+
171
+ ## Configuration
172
+
173
+ Resolved in order: environment variables > `./backtestchat.toml` > defaults.
174
+
175
+ | Key | Env var | Default |
176
+ |---|---|---|
177
+ | model | `BACKTESTCHAT_MODEL` | (unset; required for chat) |
178
+ | data dir | `BACKTESTCHAT_DATA_DIR` | `./data` |
179
+ | artifacts dir | `BACKTESTCHAT_ARTIFACTS_DIR` | `./artifacts` |
180
+
181
+ `./data` holds the sqlite store (strategies, paper runs, trades) and the
182
+ LangGraph checkpoint database. `./artifacts` holds rendered chart HTML. Both are
183
+ gitignored.
184
+
185
+ ## Development
186
+
187
+ ```bash
188
+ uv sync --extra openai # dev dependencies included; keep your provider extra
189
+ uv run pytest -q # deterministic, no network, no model key
190
+ ```
191
+
192
+ The engine, signals, broker window logic, and paper runner are pure and tested
193
+ against fixture bars. Market-data and agent layers are tested with mocked HTTP
194
+ and a scripted fake model, so the whole suite runs offline.
195
+
196
+ ## License
197
+
198
+ MIT - see [LICENSE](LICENSE).
@@ -0,0 +1,30 @@
1
+ backtestchat/__init__.py,sha256=L8Rctro0jN2DzQwpDcwN94pNIxiWrvDOb8kE-cxUbKw,277
2
+ backtestchat/__main__.py,sha256=uvGwUv7A2HsYAeQQv5M3uyah6RIF-9BJ9JKFHgPXtDE,111
3
+ backtestchat/artifacts.py,sha256=uJ4TI6Vxs4P697M02zDBfFBQX5JhqBiGVG_JFNa7DeM,1781
4
+ backtestchat/cli.py,sha256=LdpTHeA6tRaC_tPGNVhKxp4Hbdp9BHl0Lp3f9xWp8DA,6447
5
+ backtestchat/config.py,sha256=oDCFVOrZnrF-YqsBuajToM1VKZLEz3Cb5sxPOAhPuJg,2663
6
+ backtestchat/agent/__init__.py,sha256=zsRu0ZAkeCL4Ut4vMle4OqEIyxbMmFTHFTYNyFa49-4,467
7
+ backtestchat/agent/chat.py,sha256=w5dwXF5-zD6A7SIjR6EJgb0veYNxE_WUIssLLh2ErXQ,7852
8
+ backtestchat/agent/context.py,sha256=svD4OnnFdK4RHIcubE1T36xIz9Siib_36uoE1tqnXHc,2326
9
+ backtestchat/agent/graph.py,sha256=OwkG3ljsXv60tv4f5u1ZLi2uYJnbIMjl5w2ye6fVR-o,4270
10
+ backtestchat/agent/prompt.py,sha256=2QPXgbQAO9P4QpFZxphp5y1ll7gAA6_JdAMDRLgEbLg,2625
11
+ backtestchat/agent/tools.py,sha256=910HHHjrneX77yREKaoEvAUZqwQXp-vyc3QEPAtsn_8,16324
12
+ backtestchat/broker/__init__.py,sha256=vHHL56FCb2D98fngGZILaKwFOJ_5Mkh4khFjgc2B97o,883
13
+ backtestchat/broker/base.py,sha256=Y7VLjrVkQincgmNMDiYp7B9cnKcDBXHGPqyVJL1B3F0,8253
14
+ backtestchat/broker/binance.py,sha256=6SEhAvSrON6a0vOTF_-u_Wsl1p-zQVUyPGAII75pPMk,9729
15
+ backtestchat/paper/__init__.py,sha256=ML1jQjmfokSiabpWK_NNW4K4CnoaRjz1YJnBXA63PHs,440
16
+ backtestchat/paper/runner.py,sha256=DqOmWGW8NwVllDK6SMvOcL98zRLu0UmvlEDxCtzM_po,8146
17
+ backtestchat/paper/store.py,sha256=8DuI00C76aJXS7dP7CU46FCHQCVEMIdOxaJd82tXvNo,7694
18
+ backtestchat/render/__init__.py,sha256=BpW1kmQ7qpbyzjgucrbj_u2feSnDkV_Ul0xOgPJUDQo,632
19
+ backtestchat/render/plotly_render.py,sha256=baVSqJtudZGJw8bPWI_dvtaS6gNZXcWm-8mfpmItZQQ,7069
20
+ backtestchat/render/widgets.py,sha256=uelPHY6qj8zW-Jk1vbSkBPti_gE0Z1fumA839Wnr7jQ,3350
21
+ backtestchat/strategy/__init__.py,sha256=BwP82fKE9swhXX92AiNBqV1Ty58jYNq_hsvkzduL5Jg,662
22
+ backtestchat/strategy/engine.py,sha256=jp4UheuXtxEQwhjdzsh0ZSmtjCjnq_wm_aPJUZRCHnI,6095
23
+ backtestchat/strategy/indicators.py,sha256=JH5UhQF42k8NVCyDTThn_Xaf2pLgr3BTyo9TU5W0qZg,4907
24
+ backtestchat/strategy/registry.py,sha256=dAss6YEq-TolUrGqXtU5vZvLU-N-5ZsZWTyYm-sYGoA,10326
25
+ backtestchat/strategy/signals.py,sha256=BB8PkiZRa3c8Q31zFKmZnK0skcDZsnwWP4KG-nSaylg,9200
26
+ backtestchat-0.1.0.dist-info/METADATA,sha256=m_fsK4CoKcoJErIy6he7B8lyKuFl6X2xCQhKHgdW9NU,7654
27
+ backtestchat-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
28
+ backtestchat-0.1.0.dist-info/entry_points.txt,sha256=BUUDC4t3lf9mvV1jOFtmbT0J1xEzmR_MYsTwTUv0vSs,84
29
+ backtestchat-0.1.0.dist-info/licenses/LICENSE,sha256=IvWZgG54IXnO0mwORHbH0nVGd7cjsly8pAPcc2mcB10,1067
30
+ backtestchat-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ backtestchat = backtestchat.cli:app
3
+ btchat = backtestchat.cli:app
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Baris Arat
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.