cotmetrics 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.
- cotmetrics-0.1.0/LICENSE +21 -0
- cotmetrics-0.1.0/PKG-INFO +75 -0
- cotmetrics-0.1.0/README.md +43 -0
- cotmetrics-0.1.0/pyproject.toml +52 -0
- cotmetrics-0.1.0/setup.cfg +4 -0
- cotmetrics-0.1.0/src/cotmetrics/CotDatabase.py +220 -0
- cotmetrics-0.1.0/src/cotmetrics/CotIndexer.py +1707 -0
- cotmetrics-0.1.0/src/cotmetrics/__init__.py +12 -0
- cotmetrics-0.1.0/src/cotmetrics/conditions.py +395 -0
- cotmetrics-0.1.0/src/cotmetrics/config.py +53 -0
- cotmetrics-0.1.0/src/cotmetrics/constants.py +527 -0
- cotmetrics-0.1.0/src/cotmetrics/database.py +9 -0
- cotmetrics-0.1.0/src/cotmetrics/etl.py +149 -0
- cotmetrics-0.1.0/src/cotmetrics/indexer.py +73 -0
- cotmetrics-0.1.0/src/cotmetrics/indicators.py +294 -0
- cotmetrics-0.1.0/src/cotmetrics/market_data.py +28 -0
- cotmetrics-0.1.0/src/cotmetrics/models.py +174 -0
- cotmetrics-0.1.0/src/cotmetrics/movers.py +287 -0
- cotmetrics-0.1.0/src/cotmetrics/options_data.py +376 -0
- cotmetrics-0.1.0/src/cotmetrics/params.yaml +41 -0
- cotmetrics-0.1.0/src/cotmetrics/pipelines/01_etl_downloader.py +56 -0
- cotmetrics-0.1.0/src/cotmetrics/pipelines/__init__.py +0 -0
- cotmetrics-0.1.0/src/cotmetrics/pipelines/etl_scheduler.py +235 -0
- cotmetrics-0.1.0/src/cotmetrics/report_styles.py +225 -0
- cotmetrics-0.1.0/src/cotmetrics/reports.py +289 -0
- cotmetrics-0.1.0/src/cotmetrics/signals.py +1091 -0
- cotmetrics-0.1.0/src/cotmetrics/symbol_code_map.py +613 -0
- cotmetrics-0.1.0/src/cotmetrics/synthesis.py +278 -0
- cotmetrics-0.1.0/src/cotmetrics/utils.py +260 -0
- cotmetrics-0.1.0/src/cotmetrics.egg-info/PKG-INFO +75 -0
- cotmetrics-0.1.0/src/cotmetrics.egg-info/SOURCES.txt +47 -0
- cotmetrics-0.1.0/src/cotmetrics.egg-info/dependency_links.txt +1 -0
- cotmetrics-0.1.0/src/cotmetrics.egg-info/requires.txt +21 -0
- cotmetrics-0.1.0/src/cotmetrics.egg-info/top_level.txt +1 -0
- cotmetrics-0.1.0/tests/test_basis.py +202 -0
- cotmetrics-0.1.0/tests/test_conditions.py +245 -0
- cotmetrics-0.1.0/tests/test_etl.py +124 -0
- cotmetrics-0.1.0/tests/test_etl_scheduler_rebuild.py +67 -0
- cotmetrics-0.1.0/tests/test_index_ramp.py +317 -0
- cotmetrics-0.1.0/tests/test_indexer_lazy.py +88 -0
- cotmetrics-0.1.0/tests/test_indicators.py +94 -0
- cotmetrics-0.1.0/tests/test_is_setup.py +69 -0
- cotmetrics-0.1.0/tests/test_models.py +307 -0
- cotmetrics-0.1.0/tests/test_movers.py +303 -0
- cotmetrics-0.1.0/tests/test_obos_concentration.py +220 -0
- cotmetrics-0.1.0/tests/test_options_cache.py +115 -0
- cotmetrics-0.1.0/tests/test_price_trend.py +78 -0
- cotmetrics-0.1.0/tests/test_roles.py +104 -0
- cotmetrics-0.1.0/tests/test_signals.py +175 -0
cotmetrics-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Matt Spinola
|
|
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,75 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cotmetrics
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Turn raw COT data into positioning metrics and trading signals (positioning index, concentration/clustering/position-size, reversal signals).
|
|
5
|
+
Author-email: "Matt S." <cotanalyzer@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/mspinola/cotmetrics
|
|
8
|
+
Project-URL: Repository, https://github.com/mspinola/cotmetrics
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.9
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: pandas>=1.5
|
|
16
|
+
Requires-Dist: numpy
|
|
17
|
+
Requires-Dist: pyarrow>=10
|
|
18
|
+
Requires-Dist: PyYAML>=6.0
|
|
19
|
+
Requires-Dist: pytz
|
|
20
|
+
Requires-Dist: requests>=2.28
|
|
21
|
+
Requires-Dist: xlrd>=2.0
|
|
22
|
+
Requires-Dist: cotdata>=0.1.0
|
|
23
|
+
Provides-Extra: options
|
|
24
|
+
Requires-Dist: yfinance; extra == "options"
|
|
25
|
+
Provides-Extra: scheduler
|
|
26
|
+
Requires-Dist: schedule; extra == "scheduler"
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
29
|
+
Requires-Dist: ruff==0.15.22; extra == "dev"
|
|
30
|
+
Requires-Dist: tomli; python_version < "3.11" and extra == "dev"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
# cotmetrics
|
|
34
|
+
|
|
35
|
+
Turn raw COT (Commitments of Traders) data into positioning metrics and trading
|
|
36
|
+
signals — the positioning index, concentration / clustering / position-size,
|
|
37
|
+
reversal signals, and the `CotIndexer` that assembles per-instrument weekly panels.
|
|
38
|
+
|
|
39
|
+
Split out of `cot-analyzer` so the metrics layer installs without the Dash/Plotly
|
|
40
|
+
dashboard stack. Reads prices/COT from the shared [`cotdata`](../cotdata) store.
|
|
41
|
+
|
|
42
|
+
## Install (workspace, editable)
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install -e ../cotdata -e .[options,scheduler,dev]
|
|
46
|
+
export COTDATA_STORE=~/code/cotdata_store # shared data store
|
|
47
|
+
export COTMETRICS_CACHE=~/.cache/cotmetrics # derived per-instrument parquet cache
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Use
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
import cotmetrics # flat metric fns
|
|
54
|
+
from cotmetrics.indexer import cotIndexer, boot_options_update
|
|
55
|
+
from cotmetrics.signals import append_trading_signals
|
|
56
|
+
cotmetrics.calculate_cot_index(...)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`import cotmetrics` is side-effect-free. Constructing the indexer
|
|
60
|
+
(`from cotmetrics.indexer import cotIndexer`) loads the store; the daily options
|
|
61
|
+
fetch runs only when you call `boot_options_update()` explicitly.
|
|
62
|
+
|
|
63
|
+
## Config / paths
|
|
64
|
+
|
|
65
|
+
| env | meaning | default |
|
|
66
|
+
|-----|---------|---------|
|
|
67
|
+
| `COTDATA_STORE` | shared price/COT store (from cotdata) | required |
|
|
68
|
+
| `COTMETRICS_CACHE` | derived per-instrument parquet cache | `~/.cache/cotmetrics` |
|
|
69
|
+
| `COTMETRICS_PARAMS` | instrument/params config | packaged **sample** `params.yaml` |
|
|
70
|
+
| `COTMETRICS_DATA` | legacy raw_cot_data.parquet + real_test_data exports | `<cache>/../cotmetrics_data` |
|
|
71
|
+
|
|
72
|
+
The packaged `params.yaml` is a small **generic sample** (a handful of well-known
|
|
73
|
+
symbols, untuned 52-week lookbacks) so cotmetrics runs out of the box. For a real
|
|
74
|
+
instrument universe and any tuned parameters, set `COTMETRICS_PARAMS` to your own
|
|
75
|
+
config file.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# cotmetrics
|
|
2
|
+
|
|
3
|
+
Turn raw COT (Commitments of Traders) data into positioning metrics and trading
|
|
4
|
+
signals — the positioning index, concentration / clustering / position-size,
|
|
5
|
+
reversal signals, and the `CotIndexer` that assembles per-instrument weekly panels.
|
|
6
|
+
|
|
7
|
+
Split out of `cot-analyzer` so the metrics layer installs without the Dash/Plotly
|
|
8
|
+
dashboard stack. Reads prices/COT from the shared [`cotdata`](../cotdata) store.
|
|
9
|
+
|
|
10
|
+
## Install (workspace, editable)
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install -e ../cotdata -e .[options,scheduler,dev]
|
|
14
|
+
export COTDATA_STORE=~/code/cotdata_store # shared data store
|
|
15
|
+
export COTMETRICS_CACHE=~/.cache/cotmetrics # derived per-instrument parquet cache
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Use
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
import cotmetrics # flat metric fns
|
|
22
|
+
from cotmetrics.indexer import cotIndexer, boot_options_update
|
|
23
|
+
from cotmetrics.signals import append_trading_signals
|
|
24
|
+
cotmetrics.calculate_cot_index(...)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`import cotmetrics` is side-effect-free. Constructing the indexer
|
|
28
|
+
(`from cotmetrics.indexer import cotIndexer`) loads the store; the daily options
|
|
29
|
+
fetch runs only when you call `boot_options_update()` explicitly.
|
|
30
|
+
|
|
31
|
+
## Config / paths
|
|
32
|
+
|
|
33
|
+
| env | meaning | default |
|
|
34
|
+
|-----|---------|---------|
|
|
35
|
+
| `COTDATA_STORE` | shared price/COT store (from cotdata) | required |
|
|
36
|
+
| `COTMETRICS_CACHE` | derived per-instrument parquet cache | `~/.cache/cotmetrics` |
|
|
37
|
+
| `COTMETRICS_PARAMS` | instrument/params config | packaged **sample** `params.yaml` |
|
|
38
|
+
| `COTMETRICS_DATA` | legacy raw_cot_data.parquet + real_test_data exports | `<cache>/../cotmetrics_data` |
|
|
39
|
+
|
|
40
|
+
The packaged `params.yaml` is a small **generic sample** (a handful of well-known
|
|
41
|
+
symbols, untuned 52-week lookbacks) so cotmetrics runs out of the box. For a real
|
|
42
|
+
instrument universe and any tuned parameters, set `COTMETRICS_PARAMS` to your own
|
|
43
|
+
config file.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "cotmetrics"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Turn raw COT data into positioning metrics and trading signals (positioning index, concentration/clustering/position-size, reversal signals)."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
authors = [
|
|
12
|
+
{name = "Matt S.", email = "cotanalyzer@gmail.com"}
|
|
13
|
+
]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
]
|
|
19
|
+
requires-python = ">=3.9"
|
|
20
|
+
dependencies = [
|
|
21
|
+
"pandas>=1.5",
|
|
22
|
+
"numpy",
|
|
23
|
+
"pyarrow>=10",
|
|
24
|
+
"PyYAML>=6.0",
|
|
25
|
+
"pytz",
|
|
26
|
+
"requests>=2.28", # legacy CFTC ETL (cotmetrics.etl)
|
|
27
|
+
"xlrd>=2.0", # legacy CFTC dea_fut_xls are .xls
|
|
28
|
+
"cotdata>=0.1.0", # shared data layer; editable in the workspace: -e ../cotdata
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.optional-dependencies]
|
|
32
|
+
options = ["yfinance"] # cotmetrics.options_data (max-pain snapshots)
|
|
33
|
+
scheduler = ["schedule"] # cotmetrics.pipelines.etl_scheduler
|
|
34
|
+
dev = ["pytest>=7", "ruff==0.15.22", 'tomli; python_version < "3.11"'] # tomli for scripts/check_dep_floors.py on <3.11
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Homepage = "https://github.com/mspinola/cotmetrics"
|
|
38
|
+
Repository = "https://github.com/mspinola/cotmetrics"
|
|
39
|
+
|
|
40
|
+
[tool.setuptools.packages.find]
|
|
41
|
+
where = ["src"]
|
|
42
|
+
|
|
43
|
+
[tool.setuptools.package-data]
|
|
44
|
+
cotmetrics = ["*.yaml"]
|
|
45
|
+
|
|
46
|
+
[tool.ruff]
|
|
47
|
+
line-length = 100
|
|
48
|
+
target-version = "py39"
|
|
49
|
+
|
|
50
|
+
[tool.ruff.lint]
|
|
51
|
+
select = ["E", "F", "W", "I"]
|
|
52
|
+
ignore = ["E501"]
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sqlite3
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
from zoneinfo import ZoneInfo
|
|
5
|
+
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
import cotmetrics.utils as utils
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CotDatabase:
|
|
12
|
+
"""Class to manage COT data"""
|
|
13
|
+
def __init__(self, db_name=None):
|
|
14
|
+
if db_name is None:
|
|
15
|
+
# Resolve absolutely to cot-analyzer/data/cot_data.db
|
|
16
|
+
base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
|
17
|
+
self.db_name = os.path.join(base_dir, 'data', 'cot_data.db')
|
|
18
|
+
else:
|
|
19
|
+
self.db_name = db_name
|
|
20
|
+
|
|
21
|
+
# Ensure directories exist
|
|
22
|
+
os.makedirs(os.path.dirname(self.db_name), exist_ok=True)
|
|
23
|
+
|
|
24
|
+
self.setup_database()
|
|
25
|
+
|
|
26
|
+
def setup_database(self):
|
|
27
|
+
"""Create the database and the necessary table if it doesn't exist."""
|
|
28
|
+
conn = sqlite3.connect(self.db_name)
|
|
29
|
+
c = conn.cursor()
|
|
30
|
+
c.execute('''
|
|
31
|
+
CREATE TABLE IF NOT EXISTS zip_files (
|
|
32
|
+
year INTEGER PRIMARY KEY,
|
|
33
|
+
last_modified TEXT
|
|
34
|
+
)
|
|
35
|
+
''')
|
|
36
|
+
|
|
37
|
+
# Add visitor logs table
|
|
38
|
+
c.execute('''
|
|
39
|
+
CREATE TABLE IF NOT EXISTS visitor_logs (
|
|
40
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
41
|
+
timestamp TEXT,
|
|
42
|
+
ip_address TEXT,
|
|
43
|
+
path TEXT,
|
|
44
|
+
user_agent TEXT,
|
|
45
|
+
city TEXT,
|
|
46
|
+
country TEXT
|
|
47
|
+
)
|
|
48
|
+
''')
|
|
49
|
+
|
|
50
|
+
# Add ml_predictions_v2 table
|
|
51
|
+
c.execute('''
|
|
52
|
+
CREATE TABLE IF NOT EXISTS ml_predictions_v2 (
|
|
53
|
+
symbol TEXT,
|
|
54
|
+
report_date TEXT,
|
|
55
|
+
prob_success REAL,
|
|
56
|
+
meta_side INTEGER,
|
|
57
|
+
expectancy REAL,
|
|
58
|
+
atr_mult_tp REAL,
|
|
59
|
+
atr_mult_sl REAL,
|
|
60
|
+
updated_at TEXT,
|
|
61
|
+
PRIMARY KEY (symbol, report_date)
|
|
62
|
+
)
|
|
63
|
+
''')
|
|
64
|
+
|
|
65
|
+
# Safely add new columns to existing table
|
|
66
|
+
for col, col_type in [('expectancy', 'REAL'), ('atr_mult_tp', 'REAL'), ('atr_mult_sl', 'REAL')]:
|
|
67
|
+
try:
|
|
68
|
+
c.execute(f'ALTER TABLE ml_predictions_v2 ADD COLUMN {col} {col_type}')
|
|
69
|
+
except sqlite3.OperationalError:
|
|
70
|
+
pass
|
|
71
|
+
conn.commit()
|
|
72
|
+
conn.close()
|
|
73
|
+
|
|
74
|
+
def log_visit(self, ip, path, ua, city="Unknown", country="Unknown"):
|
|
75
|
+
"""Records a new visitor event to the database."""
|
|
76
|
+
conn = sqlite3.connect(self.db_name)
|
|
77
|
+
c = conn.cursor()
|
|
78
|
+
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
79
|
+
c.execute('''
|
|
80
|
+
INSERT INTO visitor_logs (timestamp, ip_address, path, user_agent, city, country)
|
|
81
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
82
|
+
''', (now, ip, path, ua, city, country))
|
|
83
|
+
conn.commit()
|
|
84
|
+
conn.close()
|
|
85
|
+
|
|
86
|
+
def get_visitor_stats(self):
|
|
87
|
+
"""Retrieves recent logs for the admin dashboard."""
|
|
88
|
+
conn = sqlite3.connect(self.db_name)
|
|
89
|
+
df = pd.read_sql_query("SELECT * FROM visitor_logs ORDER BY id DESC LIMIT 500", conn)
|
|
90
|
+
conn.close()
|
|
91
|
+
return df
|
|
92
|
+
|
|
93
|
+
def update_zip_file(self, year, last_modified):
|
|
94
|
+
"""Update the last modified date of the zip file in the database."""
|
|
95
|
+
conn = sqlite3.connect(self.db_name)
|
|
96
|
+
c = conn.cursor()
|
|
97
|
+
c.execute('''
|
|
98
|
+
INSERT INTO zip_files (year, last_modified) VALUES (?, ?)
|
|
99
|
+
ON CONFLICT(year) DO UPDATE SET last_modified = ?
|
|
100
|
+
''', (year, last_modified, last_modified))
|
|
101
|
+
conn.commit()
|
|
102
|
+
conn.close()
|
|
103
|
+
utils.get_cot_logger().warning(f"Updated latest zipfile time in DB {year} {last_modified}")
|
|
104
|
+
|
|
105
|
+
def get_zipfile_last_modified_time(self, year):
|
|
106
|
+
conn = sqlite3.connect(self.db_name)
|
|
107
|
+
c = conn.cursor()
|
|
108
|
+
c.execute('SELECT last_modified FROM zip_files WHERE year = ?', (year,))
|
|
109
|
+
row = c.fetchone()
|
|
110
|
+
conn.close()
|
|
111
|
+
|
|
112
|
+
result = None
|
|
113
|
+
if row:
|
|
114
|
+
try:
|
|
115
|
+
result = datetime.strptime(row[0], '%a, %d %b %Y %H:%M:%S %Z')
|
|
116
|
+
except Exception as e:
|
|
117
|
+
utils.get_cot_logger().debug(f"First attempt exception in date format {result}. {e}")
|
|
118
|
+
try:
|
|
119
|
+
utils.get_cot_logger().debug(f"2nd attempt on {row[0]}")
|
|
120
|
+
result = datetime.strptime(row[0], '%Y-%m-%d %H:%M:%S')
|
|
121
|
+
utils.get_cot_logger().debug(f"2nd time worked {result}")
|
|
122
|
+
except Exception as e:
|
|
123
|
+
utils.get_cot_logger().error(f"2nd time exception in date format {result}. {e}")
|
|
124
|
+
return None
|
|
125
|
+
return result
|
|
126
|
+
|
|
127
|
+
def latest_update_timestamp(self):
|
|
128
|
+
tz_aware = "Unknown"
|
|
129
|
+
year = str(datetime.now().year)
|
|
130
|
+
result = self.get_zipfile_last_modified_time(year)
|
|
131
|
+
if result is not None:
|
|
132
|
+
tz_aware = result.replace(tzinfo=timezone.utc)
|
|
133
|
+
tz_aware = tz_aware.astimezone(ZoneInfo("America/New_York"))
|
|
134
|
+
tz_aware = tz_aware.strftime("%Y-%m-%d %H:%M:%S %Z")
|
|
135
|
+
if tz_aware is None:
|
|
136
|
+
tz_aware = "Unknown"
|
|
137
|
+
return tz_aware
|
|
138
|
+
|
|
139
|
+
def save_predictions(self, symbol, report_date, prob_success, meta_side, expectancy=None, atr_mult_tp=None, atr_mult_sl=None):
|
|
140
|
+
conn = sqlite3.connect(self.db_name)
|
|
141
|
+
c = conn.cursor()
|
|
142
|
+
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
143
|
+
c.execute('''
|
|
144
|
+
INSERT INTO ml_predictions_v2 (
|
|
145
|
+
symbol, report_date, prob_success, meta_side, expectancy, atr_mult_tp, atr_mult_sl, updated_at
|
|
146
|
+
)
|
|
147
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
148
|
+
ON CONFLICT(symbol, report_date) DO UPDATE SET
|
|
149
|
+
prob_success = excluded.prob_success,
|
|
150
|
+
meta_side = excluded.meta_side,
|
|
151
|
+
expectancy = excluded.expectancy,
|
|
152
|
+
atr_mult_tp = excluded.atr_mult_tp,
|
|
153
|
+
atr_mult_sl = excluded.atr_mult_sl,
|
|
154
|
+
updated_at = excluded.updated_at
|
|
155
|
+
''', (symbol, report_date, prob_success, meta_side, expectancy, atr_mult_tp, atr_mult_sl, now))
|
|
156
|
+
conn.commit()
|
|
157
|
+
conn.close()
|
|
158
|
+
|
|
159
|
+
def get_latest_prediction(self, symbol):
|
|
160
|
+
conn = sqlite3.connect(self.db_name)
|
|
161
|
+
c = conn.cursor()
|
|
162
|
+
c.execute('''
|
|
163
|
+
SELECT report_date, prob_success, meta_side, updated_at
|
|
164
|
+
FROM ml_predictions_v2
|
|
165
|
+
WHERE symbol = ?
|
|
166
|
+
ORDER BY report_date DESC LIMIT 1
|
|
167
|
+
''', (symbol,))
|
|
168
|
+
row = c.fetchone()
|
|
169
|
+
conn.close()
|
|
170
|
+
if row:
|
|
171
|
+
return {
|
|
172
|
+
"report_date": row[0],
|
|
173
|
+
"prob_success": row[1],
|
|
174
|
+
"meta_side": row[2],
|
|
175
|
+
"updated_at": row[3]
|
|
176
|
+
}
|
|
177
|
+
return None
|
|
178
|
+
|
|
179
|
+
def get_prediction(self, symbol, report_date):
|
|
180
|
+
conn = sqlite3.connect(self.db_name)
|
|
181
|
+
c = conn.cursor()
|
|
182
|
+
c.execute('''
|
|
183
|
+
SELECT report_date, prob_success, meta_side, updated_at
|
|
184
|
+
FROM ml_predictions_v2
|
|
185
|
+
WHERE symbol = ? AND report_date = ?
|
|
186
|
+
''', (symbol, report_date))
|
|
187
|
+
row = c.fetchone()
|
|
188
|
+
conn.close()
|
|
189
|
+
if row:
|
|
190
|
+
return {
|
|
191
|
+
"report_date": row[0],
|
|
192
|
+
"prob_success": row[1],
|
|
193
|
+
"meta_side": row[2],
|
|
194
|
+
"updated_at": row[3]
|
|
195
|
+
}
|
|
196
|
+
return None
|
|
197
|
+
|
|
198
|
+
def get_all_predictions(self, symbol):
|
|
199
|
+
conn = sqlite3.connect(self.db_name)
|
|
200
|
+
c = conn.cursor()
|
|
201
|
+
c.execute('''
|
|
202
|
+
SELECT report_date, prob_success, meta_side, updated_at
|
|
203
|
+
FROM ml_predictions_v2
|
|
204
|
+
WHERE symbol = ?
|
|
205
|
+
ORDER BY report_date ASC
|
|
206
|
+
''', (symbol,))
|
|
207
|
+
rows = c.fetchall()
|
|
208
|
+
conn.close()
|
|
209
|
+
|
|
210
|
+
predictions = []
|
|
211
|
+
for row in rows:
|
|
212
|
+
predictions.append({
|
|
213
|
+
"report_date": row[0],
|
|
214
|
+
"prob_success": row[1],
|
|
215
|
+
"meta_side": row[2],
|
|
216
|
+
"updated_at": row[3]
|
|
217
|
+
})
|
|
218
|
+
return predictions
|
|
219
|
+
|
|
220
|
+
|