Spaces:
Sleeping
Sleeping
added new files
Browse filesfiles to run the AI analyser
- .gitattributes +1 -0
- AIStockAnalyser.pdf +0 -0
- LICENSE +201 -0
- ReadMe.md +112 -0
- app.py +534 -0
- data_processor.py +127 -0
- database.py +266 -0
- database_console.py +344 -0
- db_console.py +290 -0
- model_trainer.py +187 -0
- pyproject.toml +18 -0
- requirements.txt +10 -0
- run_app.sh +3 -0
- simple_app.py +219 -0
- stock_trading.db +3 -0
- technical_indicators.py +225 -0
- uv.lock +0 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
stock_trading.db filter=lfs diff=lfs merge=lfs -text
|
AIStockAnalyser.pdf
ADDED
|
Binary file (61.5 kB). View file
|
|
|
LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Apache License
|
| 2 |
+
Version 2.0, January 2004
|
| 3 |
+
http://www.apache.org/licenses/
|
| 4 |
+
|
| 5 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 6 |
+
|
| 7 |
+
1. Definitions.
|
| 8 |
+
|
| 9 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 10 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 11 |
+
|
| 12 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 13 |
+
the copyright owner that is granting the License.
|
| 14 |
+
|
| 15 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 16 |
+
other entities that control, are controlled by, or are under common
|
| 17 |
+
control with that entity. For the purposes of this definition,
|
| 18 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 19 |
+
direction or management of such entity, whether by contract or
|
| 20 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 21 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 22 |
+
|
| 23 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 24 |
+
exercising permissions granted by this License.
|
| 25 |
+
|
| 26 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 27 |
+
including but not limited to software source code, documentation
|
| 28 |
+
source, and configuration files.
|
| 29 |
+
|
| 30 |
+
"Object" form shall mean any form resulting from mechanical
|
| 31 |
+
transformation or translation of a Source form, including but
|
| 32 |
+
not limited to compiled object code, generated documentation,
|
| 33 |
+
and conversions to other media types.
|
| 34 |
+
|
| 35 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 36 |
+
Object form, made available under the License, as indicated by a
|
| 37 |
+
copyright notice that is included in or attached to the work
|
| 38 |
+
(an example is provided in the Appendix below).
|
| 39 |
+
|
| 40 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 41 |
+
form, that is based on (or derived from) the Work and for which the
|
| 42 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 43 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 44 |
+
of this License, Derivative Works shall not include works that remain
|
| 45 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 46 |
+
the Work and Derivative Works thereof.
|
| 47 |
+
|
| 48 |
+
"Contribution" shall mean any work of authorship, including
|
| 49 |
+
the original version of the Work and any modifications or additions
|
| 50 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 51 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 52 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 53 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 54 |
+
means any form of electronic, verbal, or written communication sent
|
| 55 |
+
to the Licensor or its representatives, including but not limited to
|
| 56 |
+
communication on electronic mailing lists, source code control systems,
|
| 57 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 58 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 59 |
+
excluding communication that is conspicuously marked or otherwise
|
| 60 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 61 |
+
|
| 62 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 63 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 64 |
+
subsequently incorporated within the Work.
|
| 65 |
+
|
| 66 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 67 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 68 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 69 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 70 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 71 |
+
Work and such Derivative Works in Source or Object form.
|
| 72 |
+
|
| 73 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 74 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 75 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 76 |
+
(except as stated in this section) patent license to make, have made,
|
| 77 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 78 |
+
where such license applies only to those patent claims licensable
|
| 79 |
+
by such Contributor that are necessarily infringed by their
|
| 80 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 81 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 82 |
+
institute patent litigation against any entity (including a
|
| 83 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 84 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 85 |
+
or contributory patent infringement, then any patent licenses
|
| 86 |
+
granted to You under this License for that Work shall terminate
|
| 87 |
+
as of the date such litigation is filed.
|
| 88 |
+
|
| 89 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 90 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 91 |
+
modifications, and in Source or Object form, provided that You
|
| 92 |
+
meet the following conditions:
|
| 93 |
+
|
| 94 |
+
(a) You must give any other recipients of the Work or
|
| 95 |
+
Derivative Works a copy of this License; and
|
| 96 |
+
|
| 97 |
+
(b) You must cause any modified files to carry prominent notices
|
| 98 |
+
stating that You changed the files; and
|
| 99 |
+
|
| 100 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 101 |
+
that You distribute, all copyright, patent, trademark, and
|
| 102 |
+
attribution notices from the Source form of the Work,
|
| 103 |
+
excluding those notices that do not pertain to any part of
|
| 104 |
+
the Derivative Works; and
|
| 105 |
+
|
| 106 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 107 |
+
distribution, then any Derivative Works that You distribute must
|
| 108 |
+
include a readable copy of the attribution notices contained
|
| 109 |
+
within such NOTICE file, excluding those notices that do not
|
| 110 |
+
pertain to any part of the Derivative Works, in at least one
|
| 111 |
+
of the following places: within a NOTICE text file distributed
|
| 112 |
+
as part of the Derivative Works; within the Source form or
|
| 113 |
+
documentation, if provided along with the Derivative Works; or,
|
| 114 |
+
within a display generated by the Derivative Works, if and
|
| 115 |
+
wherever such third-party notices normally appear. The contents
|
| 116 |
+
of the NOTICE file are for informational purposes only and
|
| 117 |
+
do not modify the License. You may add Your own attribution
|
| 118 |
+
notices within Derivative Works that You distribute, alongside
|
| 119 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 120 |
+
that such additional attribution notices cannot be construed
|
| 121 |
+
as modifying the License.
|
| 122 |
+
|
| 123 |
+
You may add Your own copyright statement to Your modifications and
|
| 124 |
+
may provide additional or different license terms and conditions
|
| 125 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 126 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 127 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 128 |
+
the conditions stated in this License.
|
| 129 |
+
|
| 130 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 131 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 132 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 133 |
+
this License, without any additional terms or conditions.
|
| 134 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 135 |
+
the terms of any separate license agreement you may have executed
|
| 136 |
+
with Licensor regarding such Contributions.
|
| 137 |
+
|
| 138 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 139 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 140 |
+
except as required for reasonable and customary use in describing the
|
| 141 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 142 |
+
|
| 143 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 144 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 145 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 146 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 147 |
+
implied, including, without limitation, any warranties or conditions
|
| 148 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 149 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 150 |
+
appropriateness of using or redistributing the Work and assume any
|
| 151 |
+
risks associated with Your exercise of permissions under this License.
|
| 152 |
+
|
| 153 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 154 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 155 |
+
unless required by applicable law (such as deliberate and grossly
|
| 156 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 157 |
+
liable to You for damages, including any direct, indirect, special,
|
| 158 |
+
incidental, or consequential damages of any character arising as a
|
| 159 |
+
result of this License or out of the use or inability to use the
|
| 160 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 161 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 162 |
+
other commercial damages or losses), even if such Contributor
|
| 163 |
+
has been advised of the possibility of such damages.
|
| 164 |
+
|
| 165 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 166 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 167 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 168 |
+
or other liability obligations and/or rights consistent with this
|
| 169 |
+
License. However, in accepting such obligations, You may act only
|
| 170 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 171 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 172 |
+
defend, and hold each Contributor harmless for any liability
|
| 173 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 174 |
+
of your accepting any such warranty or additional liability.
|
| 175 |
+
|
| 176 |
+
END OF TERMS AND CONDITIONS
|
| 177 |
+
|
| 178 |
+
APPENDIX: How to apply the Apache License to your work.
|
| 179 |
+
|
| 180 |
+
To apply the Apache License to your work, attach the following
|
| 181 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
| 182 |
+
replaced with your own identifying information. (Don't include
|
| 183 |
+
the brackets!) The text should be enclosed in the appropriate
|
| 184 |
+
comment syntax for the file format. We also recommend that a
|
| 185 |
+
file or class name and description of purpose be included on the
|
| 186 |
+
same "printed page" as the copyright notice for easier
|
| 187 |
+
identification within third-party archives.
|
| 188 |
+
|
| 189 |
+
Copyright [yyyy] [name of copyright owner]
|
| 190 |
+
|
| 191 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 192 |
+
you may not use this file except in compliance with the License.
|
| 193 |
+
You may obtain a copy of the License at
|
| 194 |
+
|
| 195 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 196 |
+
|
| 197 |
+
Unless required by applicable law or agreed to in writing, software
|
| 198 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 199 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 200 |
+
See the License for the specific language governing permissions and
|
| 201 |
+
limitations under the License.
|
ReadMe.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 📈 StockSignalAI
|
| 2 |
+
|
| 3 |
+
AI-powered stock analysis and signal generation tool using Streamlit.
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## 🚀 Features
|
| 8 |
+
|
| 9 |
+
- Live stock data via Yahoo Finance
|
| 10 |
+
- Technical indicators (e.g., RSI, MACD, EMA)
|
| 11 |
+
- ML-based trading signal generator
|
| 12 |
+
- Model training and evaluation
|
| 13 |
+
- Interactive UI with Streamlit
|
| 14 |
+
- SQLite-based data storage
|
| 15 |
+
|
| 16 |
+
---
|
| 17 |
+
|
| 18 |
+
## ⚙️ Setup Instructions
|
| 19 |
+
|
| 20 |
+
### 1. Clone the Repository
|
| 21 |
+
|
| 22 |
+
```bash
|
| 23 |
+
git clone https://github.com/yourusername/StockSignalAI.git
|
| 24 |
+
cd StockSignalAI
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
## 2. Create Virtual Environment (optional but recommended)
|
| 28 |
+
|
| 29 |
+
python3 -m venv venv
|
| 30 |
+
source venv/bin/activate # For macOS/Linux
|
| 31 |
+
venv\Scripts\activate # For Windows
|
| 32 |
+
3. Install Dependencies
|
| 33 |
+
|
| 34 |
+
pip install -r requirements.txt
|
| 35 |
+
If you don’t have a requirements.txt, you can create one:
|
| 36 |
+
|
| 37 |
+
txt
|
| 38 |
+
Copy
|
| 39 |
+
Edit
|
| 40 |
+
yfinance
|
| 41 |
+
pandas
|
| 42 |
+
numpy
|
| 43 |
+
matplotlib
|
| 44 |
+
scikit-learn
|
| 45 |
+
streamlit
|
| 46 |
+
ta
|
| 47 |
+
Save the above as requirements.txt, then run:
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
pip install -r requirements.txt
|
| 51 |
+
🖥️ Run the App
|
| 52 |
+
Use Streamlit, not Python directly:
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
streamlit run app.py
|
| 56 |
+
💡 If Port 8501 is Busy
|
| 57 |
+
|
| 58 |
+
streamlit run app.py --server.port 8888
|
| 59 |
+
🧠 Notes
|
| 60 |
+
Session State Initialization
|
| 61 |
+
Before using any Streamlit session state variables (like model), initialize them at the start of the app:
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
if "model" not in st.session_state:
|
| 65 |
+
st.session_state.model = None
|
| 66 |
+
Add similar blocks for other keys like data, stock_symbol, etc.
|
| 67 |
+
|
| 68 |
+
🗃️ Database Tables Used
|
| 69 |
+
Your app uses the following tables:
|
| 70 |
+
|
| 71 |
+
stock_data
|
| 72 |
+
|
| 73 |
+
technical_indicators
|
| 74 |
+
|
| 75 |
+
trading_signals
|
| 76 |
+
|
| 77 |
+
model_metrics
|
| 78 |
+
|
| 79 |
+
Ensure your SQLite DB or ORM setup creates them before use.
|
| 80 |
+
|
| 81 |
+
🔧 Project Structure (Example)
|
| 82 |
+
|
| 83 |
+
StockSignalAI/
|
| 84 |
+
├── app.py
|
| 85 |
+
├── requirements.txt
|
| 86 |
+
├── data/
|
| 87 |
+
│ └── stocks.db
|
| 88 |
+
├── models/
|
| 89 |
+
│ └── trained_models.pkl
|
| 90 |
+
└── README.md
|
| 91 |
+
📌
|
| 92 |
+
|
| 93 |
+
TODO / Future Enhancements
|
| 94 |
+
Add backtesting module
|
| 95 |
+
|
| 96 |
+
Save models per stock - this is done
|
| 97 |
+
|
| 98 |
+
Upload and analyze user CSV data
|
| 99 |
+
|
| 100 |
+
Add performance metrics dashboard
|
| 101 |
+
|
| 102 |
+
Email/SMS alerts on new signals
|
| 103 |
+
|
| 104 |
+
🙌 Acknowledgments
|
| 105 |
+
yfinance
|
| 106 |
+
|
| 107 |
+
ta-lib
|
| 108 |
+
|
| 109 |
+
Streamlit
|
| 110 |
+
|
| 111 |
+
scikit-learn
|
| 112 |
+
|
app.py
ADDED
|
@@ -0,0 +1,534 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import pandas as pd
|
| 3 |
+
import numpy as np
|
| 4 |
+
import yfinance as yf
|
| 5 |
+
import pickle
|
| 6 |
+
import plotly.graph_objects as go
|
| 7 |
+
from plotly.subplots import make_subplots
|
| 8 |
+
import os
|
| 9 |
+
from datetime import datetime, timedelta
|
| 10 |
+
import warnings
|
| 11 |
+
warnings.filterwarnings('ignore')
|
| 12 |
+
|
| 13 |
+
# Import custom modules
|
| 14 |
+
from data_processor import DataProcessor
|
| 15 |
+
from technical_indicators import TechnicalIndicators
|
| 16 |
+
from model_trainer import ModelTrainer
|
| 17 |
+
from database import DatabaseManager
|
| 18 |
+
|
| 19 |
+
# Configure page
|
| 20 |
+
st.set_page_config(
|
| 21 |
+
page_title="AI Stock Trading Signals",
|
| 22 |
+
page_icon="📈",
|
| 23 |
+
layout="wide"
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
# Initialize session state
|
| 27 |
+
if 'model_trained' not in st.session_state:
|
| 28 |
+
st.session_state.model_trained = False
|
| 29 |
+
if 'model' not in st.session_state:
|
| 30 |
+
st.session_state.model = None
|
| 31 |
+
if 'feature_columns' not in st.session_state:
|
| 32 |
+
st.session_state.feature_columns = None
|
| 33 |
+
if 'db_manager' not in st.session_state:
|
| 34 |
+
st.session_state.db_manager = DatabaseManager()
|
| 35 |
+
|
| 36 |
+
def load_or_train_model(stock_symbol):
|
| 37 |
+
"""Load existing model or train new one"""
|
| 38 |
+
model_file = f"model_{stock_symbol.replace('.', '_')}.pkl"
|
| 39 |
+
|
| 40 |
+
if os.path.exists(model_file) and st.session_state.model is None:
|
| 41 |
+
try:
|
| 42 |
+
with open(model_file, 'rb') as f:
|
| 43 |
+
model_data = pickle.load(f)
|
| 44 |
+
st.session_state.model = model_data['model']
|
| 45 |
+
st.session_state.feature_columns = model_data['feature_columns']
|
| 46 |
+
st.session_state.model_trained = True
|
| 47 |
+
return True
|
| 48 |
+
except Exception as e:
|
| 49 |
+
st.error(f"Error loading model: {str(e)}")
|
| 50 |
+
return False
|
| 51 |
+
|
| 52 |
+
return st.session_state.model_trained
|
| 53 |
+
|
| 54 |
+
def train_new_model(stock_symbol):
|
| 55 |
+
"""Train a new model for the selected stock"""
|
| 56 |
+
with st.spinner("Training AI model... This may take a few minutes."):
|
| 57 |
+
try:
|
| 58 |
+
# Initialize components
|
| 59 |
+
data_processor = DataProcessor()
|
| 60 |
+
tech_indicators = TechnicalIndicators()
|
| 61 |
+
model_trainer = ModelTrainer()
|
| 62 |
+
|
| 63 |
+
# Download and process data
|
| 64 |
+
data = data_processor.download_stock_data(stock_symbol, period="1y")
|
| 65 |
+
if data is None or len(data) < 100:
|
| 66 |
+
st.error("Insufficient data for training. Please try another stock.")
|
| 67 |
+
return False
|
| 68 |
+
|
| 69 |
+
# Save stock data to database
|
| 70 |
+
st.session_state.db_manager.save_stock_data(stock_symbol, data)
|
| 71 |
+
|
| 72 |
+
# Calculate technical indicators
|
| 73 |
+
data_with_indicators = tech_indicators.calculate_all_indicators(data)
|
| 74 |
+
|
| 75 |
+
# Save technical indicators to database
|
| 76 |
+
st.session_state.db_manager.save_technical_indicators(stock_symbol, data_with_indicators)
|
| 77 |
+
|
| 78 |
+
# Prepare features and labels
|
| 79 |
+
features, labels = data_processor.prepare_features_and_labels(data_with_indicators)
|
| 80 |
+
|
| 81 |
+
if len(features) == 0:
|
| 82 |
+
st.error("No valid features could be generated for training.")
|
| 83 |
+
return False
|
| 84 |
+
|
| 85 |
+
# Train model
|
| 86 |
+
model, feature_columns, metrics = model_trainer.train_model(features, labels)
|
| 87 |
+
|
| 88 |
+
# Save model performance to database
|
| 89 |
+
st.session_state.db_manager.save_model_metrics(
|
| 90 |
+
stock_symbol,
|
| 91 |
+
"RandomForest",
|
| 92 |
+
metrics['accuracy'],
|
| 93 |
+
metrics['precision'],
|
| 94 |
+
metrics['recall'],
|
| 95 |
+
len(feature_columns),
|
| 96 |
+
len(features)
|
| 97 |
+
)
|
| 98 |
+
|
| 99 |
+
# Save model
|
| 100 |
+
model_file = f"model_{stock_symbol.replace('.', '_')}.pkl"
|
| 101 |
+
with open(model_file, 'wb') as f:
|
| 102 |
+
pickle.dump({
|
| 103 |
+
'model': model,
|
| 104 |
+
'feature_columns': feature_columns,
|
| 105 |
+
'metrics': metrics
|
| 106 |
+
}, f)
|
| 107 |
+
|
| 108 |
+
# Update session state
|
| 109 |
+
st.session_state.model = model
|
| 110 |
+
st.session_state.feature_columns = feature_columns
|
| 111 |
+
st.session_state.model_trained = True
|
| 112 |
+
|
| 113 |
+
# Display training metrics
|
| 114 |
+
st.success("Model trained successfully!")
|
| 115 |
+
col1, col2, col3 = st.columns(3)
|
| 116 |
+
with col1:
|
| 117 |
+
st.metric("Accuracy", f"{metrics['accuracy']:.3f}")
|
| 118 |
+
with col2:
|
| 119 |
+
st.metric("Precision", f"{metrics['precision']:.3f}")
|
| 120 |
+
with col3:
|
| 121 |
+
st.metric("Recall", f"{metrics['recall']:.3f}")
|
| 122 |
+
|
| 123 |
+
return True
|
| 124 |
+
|
| 125 |
+
except Exception as e:
|
| 126 |
+
st.error(f"Error training model: {str(e)}")
|
| 127 |
+
return False
|
| 128 |
+
|
| 129 |
+
def get_live_price(stock_symbol):
|
| 130 |
+
"""Get live stock price"""
|
| 131 |
+
try:
|
| 132 |
+
# Try to get live price using nsepython
|
| 133 |
+
try:
|
| 134 |
+
from nsepython import nsefetch
|
| 135 |
+
# Extract stock name without .NS suffix
|
| 136 |
+
stock_name = stock_symbol.replace('.NS', '')
|
| 137 |
+
url = f"https://www.nseindia.com/api/quote-equity?symbol={stock_name}"
|
| 138 |
+
live_data = nsefetch(url)
|
| 139 |
+
if live_data and 'priceInfo' in live_data and 'lastPrice' in live_data['priceInfo']:
|
| 140 |
+
return float(live_data['priceInfo']['lastPrice'])
|
| 141 |
+
except:
|
| 142 |
+
pass
|
| 143 |
+
|
| 144 |
+
# Fallback to yfinance
|
| 145 |
+
ticker = yf.Ticker(stock_symbol)
|
| 146 |
+
data = ticker.history(period="1d", interval="1m")
|
| 147 |
+
if not data.empty:
|
| 148 |
+
return float(data['Close'].iloc[-1])
|
| 149 |
+
|
| 150 |
+
return None
|
| 151 |
+
except Exception as e:
|
| 152 |
+
st.warning(f"Could not fetch live price: {str(e)}")
|
| 153 |
+
return None
|
| 154 |
+
|
| 155 |
+
def predict_signal(stock_symbol):
|
| 156 |
+
"""Predict trading signal for the stock"""
|
| 157 |
+
try:
|
| 158 |
+
# Initialize components
|
| 159 |
+
data_processor = DataProcessor()
|
| 160 |
+
tech_indicators = TechnicalIndicators()
|
| 161 |
+
|
| 162 |
+
# Get recent data for prediction
|
| 163 |
+
data = data_processor.download_stock_data(stock_symbol, period="3mo")
|
| 164 |
+
if data is None or len(data) < 50:
|
| 165 |
+
return None, "Insufficient data for prediction"
|
| 166 |
+
|
| 167 |
+
# Calculate technical indicators
|
| 168 |
+
data_with_indicators = tech_indicators.calculate_all_indicators(data)
|
| 169 |
+
|
| 170 |
+
# Get latest features
|
| 171 |
+
latest_features = data_processor.get_latest_features(data_with_indicators, st.session_state.feature_columns)
|
| 172 |
+
|
| 173 |
+
if latest_features is None:
|
| 174 |
+
return None, "Could not extract features from latest data"
|
| 175 |
+
|
| 176 |
+
# Make prediction
|
| 177 |
+
prediction = st.session_state.model.predict([latest_features])[0]
|
| 178 |
+
prediction_proba = st.session_state.model.predict_proba([latest_features])[0]
|
| 179 |
+
|
| 180 |
+
# Convert prediction to signal
|
| 181 |
+
signal_map = {-1: "SELL", 0: "HOLD", 1: "BUY"}
|
| 182 |
+
signal = signal_map.get(prediction, "HOLD")
|
| 183 |
+
confidence = max(prediction_proba)
|
| 184 |
+
|
| 185 |
+
# Get current live price for saving to database
|
| 186 |
+
live_price = get_live_price(stock_symbol)
|
| 187 |
+
|
| 188 |
+
# Save trading signal to database
|
| 189 |
+
if live_price and hasattr(st.session_state, 'db_manager') and st.session_state.db_manager:
|
| 190 |
+
try:
|
| 191 |
+
st.session_state.db_manager.save_trading_signal(
|
| 192 |
+
stock_symbol, signal, confidence, live_price, model_version="v1.0"
|
| 193 |
+
)
|
| 194 |
+
except Exception as e:
|
| 195 |
+
print(f"Error saving trading signal: {str(e)}")
|
| 196 |
+
|
| 197 |
+
return {
|
| 198 |
+
'signal': signal,
|
| 199 |
+
'confidence': confidence,
|
| 200 |
+
'prediction': prediction,
|
| 201 |
+
'probabilities': prediction_proba
|
| 202 |
+
}, None
|
| 203 |
+
|
| 204 |
+
except Exception as e:
|
| 205 |
+
return None, f"Error making prediction: {str(e)}"
|
| 206 |
+
|
| 207 |
+
def create_price_chart(stock_symbol):
|
| 208 |
+
"""Create interactive price chart with technical indicators"""
|
| 209 |
+
try:
|
| 210 |
+
# Initialize components
|
| 211 |
+
data_processor = DataProcessor()
|
| 212 |
+
tech_indicators = TechnicalIndicators()
|
| 213 |
+
|
| 214 |
+
# Get data for last 30 days
|
| 215 |
+
data = data_processor.download_stock_data(stock_symbol, period="2mo")
|
| 216 |
+
if data is None:
|
| 217 |
+
return None
|
| 218 |
+
|
| 219 |
+
# Calculate technical indicators
|
| 220 |
+
data_with_indicators = tech_indicators.calculate_all_indicators(data)
|
| 221 |
+
|
| 222 |
+
# Get last 30 days
|
| 223 |
+
data_chart = data_with_indicators.tail(30).copy()
|
| 224 |
+
|
| 225 |
+
# Create subplots
|
| 226 |
+
fig = make_subplots(
|
| 227 |
+
rows=4, cols=1,
|
| 228 |
+
# shared_xaxis=True,
|
| 229 |
+
shared_xaxes=True,
|
| 230 |
+
vertical_spacing=0.03,
|
| 231 |
+
subplot_titles=('Price & Bollinger Bands', 'RSI', 'MACD', 'Volume'),
|
| 232 |
+
row_heights=[0.5, 0.2, 0.2, 0.1]
|
| 233 |
+
)
|
| 234 |
+
|
| 235 |
+
# Price and Bollinger Bands
|
| 236 |
+
fig.add_trace(
|
| 237 |
+
go.Candlestick(
|
| 238 |
+
x=data_chart.index,
|
| 239 |
+
open=data_chart['Open'],
|
| 240 |
+
high=data_chart['High'],
|
| 241 |
+
low=data_chart['Low'],
|
| 242 |
+
close=data_chart['Close'],
|
| 243 |
+
name='Price'
|
| 244 |
+
), row=1, col=1
|
| 245 |
+
)
|
| 246 |
+
|
| 247 |
+
# Bollinger Bands
|
| 248 |
+
if 'BB_upper' in data_chart.columns:
|
| 249 |
+
fig.add_trace(
|
| 250 |
+
go.Scatter(x=data_chart.index, y=data_chart['BB_upper'],
|
| 251 |
+
name='BB Upper', line=dict(color='red', width=1)), row=1, col=1
|
| 252 |
+
)
|
| 253 |
+
fig.add_trace(
|
| 254 |
+
go.Scatter(x=data_chart.index, y=data_chart['BB_middle'],
|
| 255 |
+
name='BB Middle', line=dict(color='blue', width=1)), row=1, col=1
|
| 256 |
+
)
|
| 257 |
+
fig.add_trace(
|
| 258 |
+
go.Scatter(x=data_chart.index, y=data_chart['BB_lower'],
|
| 259 |
+
name='BB Lower', line=dict(color='red', width=1)), row=1, col=1
|
| 260 |
+
)
|
| 261 |
+
|
| 262 |
+
# EMA
|
| 263 |
+
if 'EMA_20' in data_chart.columns:
|
| 264 |
+
fig.add_trace(
|
| 265 |
+
go.Scatter(x=data_chart.index, y=data_chart['EMA_20'],
|
| 266 |
+
name='EMA 20', line=dict(color='orange', width=2)), row=1, col=1
|
| 267 |
+
)
|
| 268 |
+
|
| 269 |
+
# RSI
|
| 270 |
+
if 'RSI' in data_chart.columns:
|
| 271 |
+
fig.add_trace(
|
| 272 |
+
go.Scatter(x=data_chart.index, y=data_chart['RSI'],
|
| 273 |
+
name='RSI', line=dict(color='purple')), row=2, col=1
|
| 274 |
+
)
|
| 275 |
+
# RSI overbought/oversold lines
|
| 276 |
+
fig.add_hline(y=70, line_dash="dash", line_color="red", row=2, col=1)
|
| 277 |
+
fig.add_hline(y=30, line_dash="dash", line_color="green", row=2, col=1)
|
| 278 |
+
|
| 279 |
+
# MACD
|
| 280 |
+
if 'MACD' in data_chart.columns:
|
| 281 |
+
fig.add_trace(
|
| 282 |
+
go.Scatter(x=data_chart.index, y=data_chart['MACD'],
|
| 283 |
+
name='MACD', line=dict(color='blue')), row=3, col=1
|
| 284 |
+
)
|
| 285 |
+
if 'MACD_signal' in data_chart.columns:
|
| 286 |
+
fig.add_trace(
|
| 287 |
+
go.Scatter(x=data_chart.index, y=data_chart['MACD_signal'],
|
| 288 |
+
name='MACD Signal', line=dict(color='red')), row=3, col=1
|
| 289 |
+
)
|
| 290 |
+
|
| 291 |
+
# Volume
|
| 292 |
+
fig.add_trace(
|
| 293 |
+
go.Bar(x=data_chart.index, y=data_chart['Volume'],
|
| 294 |
+
name='Volume', marker_color='lightblue'), row=4, col=1
|
| 295 |
+
)
|
| 296 |
+
|
| 297 |
+
# Update layout
|
| 298 |
+
fig.update_layout(
|
| 299 |
+
title=f"{stock_symbol} - Price Chart with Technical Indicators",
|
| 300 |
+
xaxis_rangeslider_visible=False,
|
| 301 |
+
height=800,
|
| 302 |
+
showlegend=True
|
| 303 |
+
)
|
| 304 |
+
|
| 305 |
+
# Update y-axis labels
|
| 306 |
+
fig.update_yaxes(title_text="Price (₹)", row=1, col=1)
|
| 307 |
+
fig.update_yaxes(title_text="RSI", row=2, col=1)
|
| 308 |
+
fig.update_yaxes(title_text="MACD", row=3, col=1)
|
| 309 |
+
fig.update_yaxes(title_text="Volume", row=4, col=1)
|
| 310 |
+
|
| 311 |
+
return fig
|
| 312 |
+
|
| 313 |
+
except Exception as e:
|
| 314 |
+
st.error(f"Error creating chart: {str(e)}")
|
| 315 |
+
return None
|
| 316 |
+
|
| 317 |
+
def main():
|
| 318 |
+
st.title("📈 AI-Powered Stock Trading Signals")
|
| 319 |
+
st.markdown("### NSE Stock Analysis with Machine Learning")
|
| 320 |
+
|
| 321 |
+
# Initialize database manager early
|
| 322 |
+
if 'db_manager' not in st.session_state:
|
| 323 |
+
try:
|
| 324 |
+
from database import DatabaseManager
|
| 325 |
+
st.session_state.db_manager = DatabaseManager()
|
| 326 |
+
st.success("Database initialized successfully")
|
| 327 |
+
except Exception as e:
|
| 328 |
+
st.error(f"Database initialization failed: {str(e)}")
|
| 329 |
+
st.session_state.db_manager = None
|
| 330 |
+
|
| 331 |
+
# Sidebar for stock selection
|
| 332 |
+
st.sidebar.header("Stock Selection")
|
| 333 |
+
|
| 334 |
+
# Expanded NSE stock options
|
| 335 |
+
stock_options = {
|
| 336 |
+
"Reliance Industries": "RELIANCE.NS",
|
| 337 |
+
"Tata Consultancy Services": "TCS.NS",
|
| 338 |
+
"Infosys": "INFY.NS",
|
| 339 |
+
"HDFC Bank": "HDFCBANK.NS",
|
| 340 |
+
"ICICI Bank": "ICICIBANK.NS",
|
| 341 |
+
"State Bank of India": "SBIN.NS",
|
| 342 |
+
"ITC Limited": "ITC.NS",
|
| 343 |
+
"Hindustan Unilever": "HINDUNILVR.NS",
|
| 344 |
+
"Larsen & Toubro": "LT.NS",
|
| 345 |
+
"Axis Bank": "AXISBANK.NS",
|
| 346 |
+
"Kotak Mahindra Bank": "KOTAKBANK.NS",
|
| 347 |
+
"Wipro": "WIPRO.NS",
|
| 348 |
+
"Tech Mahindra": "TECHM.NS",
|
| 349 |
+
"Bharti Airtel": "BHARTIARTL.NS",
|
| 350 |
+
"Asian Paints": "ASIANPAINT.NS",
|
| 351 |
+
"Maruti Suzuki": "MARUTI.NS",
|
| 352 |
+
"Bajaj Finance": "BAJFINANCE.NS",
|
| 353 |
+
"NTPC": "NTPC.NS",
|
| 354 |
+
"Power Grid Corp": "POWERGRID.NS",
|
| 355 |
+
"Tata Steel": "TATASTEEL.NS"
|
| 356 |
+
}
|
| 357 |
+
|
| 358 |
+
# Option to enter custom stock symbol
|
| 359 |
+
st.sidebar.subheader("Or Enter Custom Stock")
|
| 360 |
+
custom_stock = st.sidebar.text_input(
|
| 361 |
+
"Enter NSE stock symbol (e.g., ADANIPORTS.NS):",
|
| 362 |
+
placeholder="SYMBOL.NS"
|
| 363 |
+
)
|
| 364 |
+
|
| 365 |
+
# Determine selected stock
|
| 366 |
+
if custom_stock and custom_stock.strip():
|
| 367 |
+
if not custom_stock.endswith('.NS'):
|
| 368 |
+
custom_stock += '.NS'
|
| 369 |
+
selected_stock = custom_stock.upper()
|
| 370 |
+
selected_stock_name = f"Custom: {selected_stock}"
|
| 371 |
+
else:
|
| 372 |
+
selected_stock_name = st.sidebar.selectbox(
|
| 373 |
+
"Choose a stock:",
|
| 374 |
+
list(stock_options.keys())
|
| 375 |
+
)
|
| 376 |
+
selected_stock = stock_options[selected_stock_name]
|
| 377 |
+
|
| 378 |
+
# Model training section
|
| 379 |
+
st.sidebar.header("Model Management")
|
| 380 |
+
|
| 381 |
+
# Check if model exists
|
| 382 |
+
model_exists = load_or_train_model(selected_stock)
|
| 383 |
+
|
| 384 |
+
if not model_exists:
|
| 385 |
+
st.sidebar.warning("No trained model found for this stock.")
|
| 386 |
+
if st.sidebar.button("Train New Model"):
|
| 387 |
+
train_new_model(selected_stock)
|
| 388 |
+
else:
|
| 389 |
+
st.sidebar.success("Model loaded successfully!")
|
| 390 |
+
if st.sidebar.button("Retrain Model"):
|
| 391 |
+
st.session_state.model = None
|
| 392 |
+
st.session_state.model_trained = False
|
| 393 |
+
train_new_model(selected_stock)
|
| 394 |
+
|
| 395 |
+
# Main content
|
| 396 |
+
if st.session_state.model_trained and st.session_state.model is not None:
|
| 397 |
+
|
| 398 |
+
# Live price section
|
| 399 |
+
st.header("📊 Live Market Data")
|
| 400 |
+
col1, col2 = st.columns(2)
|
| 401 |
+
|
| 402 |
+
with col1:
|
| 403 |
+
if st.button("🔄 Get Live Price", type="primary"):
|
| 404 |
+
live_price = get_live_price(selected_stock)
|
| 405 |
+
if live_price:
|
| 406 |
+
st.metric("Live Price (LTP)", f"₹{live_price:.2f}")
|
| 407 |
+
else:
|
| 408 |
+
st.warning("Could not fetch live price")
|
| 409 |
+
|
| 410 |
+
# Prediction section
|
| 411 |
+
st.header("🤖 AI Trading Signal")
|
| 412 |
+
|
| 413 |
+
if st.button("🎯 Get Trading Signal", type="primary"):
|
| 414 |
+
prediction_result, error = predict_signal(selected_stock)
|
| 415 |
+
|
| 416 |
+
if prediction_result:
|
| 417 |
+
signal = prediction_result['signal']
|
| 418 |
+
confidence = prediction_result['confidence']
|
| 419 |
+
|
| 420 |
+
# Display signal with color coding
|
| 421 |
+
if signal == "BUY":
|
| 422 |
+
st.success(f"🟢 **{signal}** Signal")
|
| 423 |
+
elif signal == "SELL":
|
| 424 |
+
st.error(f"🔴 **{signal}** Signal")
|
| 425 |
+
else:
|
| 426 |
+
st.info(f"🟡 **{signal}** Signal")
|
| 427 |
+
|
| 428 |
+
# Display confidence and probabilities
|
| 429 |
+
col1, col2 = st.columns(2)
|
| 430 |
+
with col1:
|
| 431 |
+
st.metric("Confidence", f"{confidence:.1%}")
|
| 432 |
+
|
| 433 |
+
with col2:
|
| 434 |
+
prob_df = pd.DataFrame({
|
| 435 |
+
'Signal': ['SELL', 'HOLD', 'BUY'],
|
| 436 |
+
'Probability': prediction_result['probabilities']
|
| 437 |
+
})
|
| 438 |
+
st.dataframe(prob_df, hide_index=True)
|
| 439 |
+
|
| 440 |
+
else:
|
| 441 |
+
st.error(f"Prediction failed: {error}")
|
| 442 |
+
|
| 443 |
+
# Chart section
|
| 444 |
+
st.header("📈 Technical Analysis Chart")
|
| 445 |
+
|
| 446 |
+
if st.button("📊 Generate Chart"):
|
| 447 |
+
chart = create_price_chart(selected_stock)
|
| 448 |
+
if chart:
|
| 449 |
+
st.plotly_chart(chart, use_container_width=True)
|
| 450 |
+
|
| 451 |
+
# Additional information
|
| 452 |
+
st.header("ℹ️ Model Information")
|
| 453 |
+
|
| 454 |
+
col1, col2 = st.columns(2)
|
| 455 |
+
with col1:
|
| 456 |
+
st.info("**Features Used:**\n- RSI, EMA, MACD\n- Bollinger Bands\n- SMA, Stochastic\n- Williams %R, ATR")
|
| 457 |
+
|
| 458 |
+
with col2:
|
| 459 |
+
st.info("**Signal Criteria:**\n- BUY: Expected 2%+ gain\n- SELL: Expected 2%+ loss\n- HOLD: Neutral expectation")
|
| 460 |
+
|
| 461 |
+
else:
|
| 462 |
+
st.info("👆 Please train a model first using the sidebar to get started with AI-powered trading signals.")
|
| 463 |
+
|
| 464 |
+
# Show sample data while waiting
|
| 465 |
+
st.header("📊 Sample Stock Data")
|
| 466 |
+
try:
|
| 467 |
+
data_processor = DataProcessor()
|
| 468 |
+
sample_data = data_processor.download_stock_data(selected_stock, period="1mo")
|
| 469 |
+
if sample_data is not None:
|
| 470 |
+
st.dataframe(sample_data.tail(10))
|
| 471 |
+
except:
|
| 472 |
+
st.warning("Could not load sample data")
|
| 473 |
+
|
| 474 |
+
# Database Information Section
|
| 475 |
+
st.sidebar.header("📊 Database Status")
|
| 476 |
+
|
| 477 |
+
# Database Console Link
|
| 478 |
+
st.sidebar.markdown("---")
|
| 479 |
+
st.sidebar.markdown("### 🗄️ Database Console")
|
| 480 |
+
st.sidebar.markdown("Access the SQLite database console:")
|
| 481 |
+
st.sidebar.markdown("**[Open Database Console](http://localhost:5001)**")
|
| 482 |
+
st.sidebar.markdown("*View tables, run SQL queries, analyze trading data*")
|
| 483 |
+
st.sidebar.markdown("*Database file: `stock_trading.db`*")
|
| 484 |
+
st.sidebar.markdown("---")
|
| 485 |
+
|
| 486 |
+
try:
|
| 487 |
+
if hasattr(st.session_state, 'db_manager') and st.session_state.db_manager:
|
| 488 |
+
db_stats = st.session_state.db_manager.get_database_stats()
|
| 489 |
+
if db_stats:
|
| 490 |
+
st.sidebar.metric("Stock Records", db_stats.get('total_stock_records', 0))
|
| 491 |
+
st.sidebar.metric("Trading Signals", db_stats.get('total_signals', 0))
|
| 492 |
+
st.sidebar.metric("Active Models", db_stats.get('active_models', 0))
|
| 493 |
+
st.sidebar.metric("Symbols Tracked", db_stats.get('symbols_tracked', 0))
|
| 494 |
+
else:
|
| 495 |
+
st.sidebar.info("Database ready - no data yet")
|
| 496 |
+
else:
|
| 497 |
+
st.sidebar.info("Database initializing...")
|
| 498 |
+
except Exception as e:
|
| 499 |
+
st.sidebar.warning("Database tables will be created when first used")
|
| 500 |
+
|
| 501 |
+
# Trading History Section
|
| 502 |
+
if st.session_state.model_trained and hasattr(st.session_state, 'db_manager') and st.session_state.db_manager:
|
| 503 |
+
st.header("📈 Trading History")
|
| 504 |
+
|
| 505 |
+
col1, col2 = st.columns(2)
|
| 506 |
+
|
| 507 |
+
with col1:
|
| 508 |
+
if st.button("View Signal History"):
|
| 509 |
+
try:
|
| 510 |
+
signal_history = st.session_state.db_manager.get_trading_signals_history(selected_stock)
|
| 511 |
+
if not signal_history.empty:
|
| 512 |
+
st.dataframe(signal_history.head(10))
|
| 513 |
+
else:
|
| 514 |
+
st.info("No trading signals found in database")
|
| 515 |
+
except Exception as e:
|
| 516 |
+
st.error(f"Error loading signal history: {str(e)}")
|
| 517 |
+
|
| 518 |
+
with col2:
|
| 519 |
+
if st.button("Model Performance"):
|
| 520 |
+
try:
|
| 521 |
+
model_perf = st.session_state.db_manager.get_model_performance(selected_stock)
|
| 522 |
+
if model_perf:
|
| 523 |
+
st.json(model_perf)
|
| 524 |
+
else:
|
| 525 |
+
st.info("No model performance data found")
|
| 526 |
+
except Exception as e:
|
| 527 |
+
st.error(f"Error loading model performance: {str(e)}")
|
| 528 |
+
|
| 529 |
+
# Database Console Access
|
| 530 |
+
st.markdown("---")
|
| 531 |
+
st.info("💡 **Tip**: Use the Database Console in the sidebar to view all stored data, run SQL queries, and analyze trading patterns across all stocks.")
|
| 532 |
+
|
| 533 |
+
if __name__ == "__main__":
|
| 534 |
+
main()
|
data_processor.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
import numpy as np
|
| 3 |
+
import yfinance as yf
|
| 4 |
+
from datetime import datetime, timedelta
|
| 5 |
+
import warnings
|
| 6 |
+
warnings.filterwarnings('ignore')
|
| 7 |
+
|
| 8 |
+
class DataProcessor:
|
| 9 |
+
def __init__(self):
|
| 10 |
+
pass
|
| 11 |
+
|
| 12 |
+
def download_stock_data(self, symbol, period="3mo", interval="1d"):
|
| 13 |
+
"""Download stock data using yfinance"""
|
| 14 |
+
try:
|
| 15 |
+
ticker = yf.Ticker(symbol)
|
| 16 |
+
data = ticker.history(period=period, interval=interval)
|
| 17 |
+
|
| 18 |
+
if data.empty:
|
| 19 |
+
return None
|
| 20 |
+
|
| 21 |
+
# Clean column names
|
| 22 |
+
data.columns = [col.title() for col in data.columns]
|
| 23 |
+
|
| 24 |
+
# Remove timezone info for consistency
|
| 25 |
+
if data.index.tz is not None:
|
| 26 |
+
data.index = data.index.tz_localize(None)
|
| 27 |
+
|
| 28 |
+
return data
|
| 29 |
+
|
| 30 |
+
except Exception as e:
|
| 31 |
+
print(f"Error downloading data for {symbol}: {str(e)}")
|
| 32 |
+
return None
|
| 33 |
+
|
| 34 |
+
def prepare_features_and_labels(self, data, future_days=3, threshold=0.02):
|
| 35 |
+
"""Prepare features and labels for machine learning"""
|
| 36 |
+
try:
|
| 37 |
+
# Calculate future returns for labeling
|
| 38 |
+
data = data.copy()
|
| 39 |
+
data['Future_Return'] = data['Close'].pct_change(periods=future_days).shift(-future_days)
|
| 40 |
+
|
| 41 |
+
# Create labels based on future returns
|
| 42 |
+
# 1 = BUY (expected gain > threshold)
|
| 43 |
+
# -1 = SELL (expected loss > threshold)
|
| 44 |
+
# 0 = HOLD (neutral)
|
| 45 |
+
conditions = [
|
| 46 |
+
data['Future_Return'] > threshold,
|
| 47 |
+
data['Future_Return'] < -threshold
|
| 48 |
+
]
|
| 49 |
+
choices = [1, -1]
|
| 50 |
+
data['Label'] = np.select(conditions, choices, default=0)
|
| 51 |
+
|
| 52 |
+
# Select feature columns (all technical indicators)
|
| 53 |
+
feature_columns = [col for col in data.columns if col not in
|
| 54 |
+
['Open', 'High', 'Low', 'Close', 'Volume', 'Future_Return', 'Label']]
|
| 55 |
+
|
| 56 |
+
# Remove rows with NaN values
|
| 57 |
+
clean_data = data.dropna()
|
| 58 |
+
|
| 59 |
+
if len(clean_data) == 0:
|
| 60 |
+
return pd.DataFrame(), pd.Series()
|
| 61 |
+
|
| 62 |
+
# Prepare features and labels
|
| 63 |
+
features = clean_data[feature_columns]
|
| 64 |
+
labels = clean_data['Label']
|
| 65 |
+
|
| 66 |
+
return features, labels
|
| 67 |
+
|
| 68 |
+
except Exception as e:
|
| 69 |
+
print(f"Error preparing features and labels: {str(e)}")
|
| 70 |
+
return pd.DataFrame(), pd.Series()
|
| 71 |
+
|
| 72 |
+
def get_latest_features(self, data, feature_columns):
|
| 73 |
+
"""Extract latest features for prediction"""
|
| 74 |
+
try:
|
| 75 |
+
if data.empty or not feature_columns:
|
| 76 |
+
return None
|
| 77 |
+
|
| 78 |
+
# Get the latest row with all required features
|
| 79 |
+
latest_data = data[feature_columns].dropna()
|
| 80 |
+
|
| 81 |
+
if latest_data.empty:
|
| 82 |
+
return None
|
| 83 |
+
|
| 84 |
+
# Return the latest row as a list
|
| 85 |
+
return latest_data.iloc[-1].values.tolist()
|
| 86 |
+
|
| 87 |
+
except Exception as e:
|
| 88 |
+
print(f"Error extracting latest features: {str(e)}")
|
| 89 |
+
return None
|
| 90 |
+
|
| 91 |
+
def calculate_returns(self, data, periods=[1, 3, 5]):
|
| 92 |
+
"""Calculate returns for different periods"""
|
| 93 |
+
try:
|
| 94 |
+
returns_data = data.copy()
|
| 95 |
+
|
| 96 |
+
for period in periods:
|
| 97 |
+
returns_data[f'Return_{period}d'] = data['Close'].pct_change(periods=period)
|
| 98 |
+
|
| 99 |
+
return returns_data
|
| 100 |
+
|
| 101 |
+
except Exception as e:
|
| 102 |
+
print(f"Error calculating returns: {str(e)}")
|
| 103 |
+
return data
|
| 104 |
+
|
| 105 |
+
def add_price_features(self, data):
|
| 106 |
+
"""Add additional price-based features"""
|
| 107 |
+
try:
|
| 108 |
+
enhanced_data = data.copy()
|
| 109 |
+
|
| 110 |
+
# Price position within daily range
|
| 111 |
+
enhanced_data['Price_Position'] = (data['Close'] - data['Low']) / (data['High'] - data['Low'])
|
| 112 |
+
enhanced_data['Price_Position'].fillna(0.5, inplace=True)
|
| 113 |
+
|
| 114 |
+
# Gap from previous close
|
| 115 |
+
enhanced_data['Gap'] = (data['Open'] - data['Close'].shift(1)) / data['Close'].shift(1)
|
| 116 |
+
|
| 117 |
+
# Intraday return
|
| 118 |
+
enhanced_data['Intraday_Return'] = (data['Close'] - data['Open']) / data['Open']
|
| 119 |
+
|
| 120 |
+
# High-Low spread
|
| 121 |
+
enhanced_data['HL_Spread'] = (data['High'] - data['Low']) / data['Close']
|
| 122 |
+
|
| 123 |
+
return enhanced_data
|
| 124 |
+
|
| 125 |
+
except Exception as e:
|
| 126 |
+
print(f"Error adding price features: {str(e)}")
|
| 127 |
+
return data
|
database.py
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sqlalchemy as sa
|
| 2 |
+
from sqlalchemy import create_engine, Column, Integer, String, Float, DateTime, Text, Boolean, text
|
| 3 |
+
from sqlalchemy.ext.declarative import declarative_base
|
| 4 |
+
from sqlalchemy.orm import sessionmaker
|
| 5 |
+
from datetime import datetime
|
| 6 |
+
import json
|
| 7 |
+
import pandas as pd
|
| 8 |
+
|
| 9 |
+
Base = declarative_base()
|
| 10 |
+
|
| 11 |
+
class StockData(Base):
|
| 12 |
+
__tablename__ = 'stock_data'
|
| 13 |
+
|
| 14 |
+
id = Column(Integer, primary_key=True)
|
| 15 |
+
symbol = Column(String(20), nullable=False)
|
| 16 |
+
date = Column(DateTime, nullable=False)
|
| 17 |
+
open_price = Column(Float)
|
| 18 |
+
high_price = Column(Float)
|
| 19 |
+
low_price = Column(Float)
|
| 20 |
+
close_price = Column(Float)
|
| 21 |
+
volume = Column(Integer)
|
| 22 |
+
created_at = Column(DateTime, default=datetime.now)
|
| 23 |
+
|
| 24 |
+
class TechnicalIndicators(Base):
|
| 25 |
+
__tablename__ = 'technical_indicators'
|
| 26 |
+
|
| 27 |
+
id = Column(Integer, primary_key=True)
|
| 28 |
+
symbol = Column(String(20), nullable=False)
|
| 29 |
+
date = Column(DateTime, nullable=False)
|
| 30 |
+
rsi = Column(Float)
|
| 31 |
+
macd = Column(Float)
|
| 32 |
+
macd_signal = Column(Float)
|
| 33 |
+
bb_upper = Column(Float)
|
| 34 |
+
bb_middle = Column(Float)
|
| 35 |
+
bb_lower = Column(Float)
|
| 36 |
+
ema_20 = Column(Float)
|
| 37 |
+
sma_20 = Column(Float)
|
| 38 |
+
sma_50 = Column(Float)
|
| 39 |
+
stoch_k = Column(Float)
|
| 40 |
+
williams_r = Column(Float)
|
| 41 |
+
atr = Column(Float)
|
| 42 |
+
created_at = Column(DateTime, default=datetime.now)
|
| 43 |
+
|
| 44 |
+
class TradingSignals(Base):
|
| 45 |
+
__tablename__ = 'trading_signals'
|
| 46 |
+
|
| 47 |
+
id = Column(Integer, primary_key=True)
|
| 48 |
+
symbol = Column(String(20), nullable=False)
|
| 49 |
+
signal_date = Column(DateTime, nullable=False)
|
| 50 |
+
signal = Column(String(10)) # BUY, SELL, HOLD
|
| 51 |
+
confidence = Column(Float)
|
| 52 |
+
live_price = Column(Float)
|
| 53 |
+
predicted_return = Column(Float)
|
| 54 |
+
model_version = Column(String(50))
|
| 55 |
+
created_at = Column(DateTime, default=datetime.now)
|
| 56 |
+
|
| 57 |
+
class ModelMetrics(Base):
|
| 58 |
+
__tablename__ = 'model_metrics'
|
| 59 |
+
|
| 60 |
+
id = Column(Integer, primary_key=True)
|
| 61 |
+
symbol = Column(String(20), nullable=False)
|
| 62 |
+
model_type = Column(String(50))
|
| 63 |
+
accuracy = Column(Float)
|
| 64 |
+
precision_score = Column(Float)
|
| 65 |
+
recall_score = Column(Float)
|
| 66 |
+
training_date = Column(DateTime, default=datetime.now)
|
| 67 |
+
feature_count = Column(Integer)
|
| 68 |
+
training_samples = Column(Integer)
|
| 69 |
+
is_active = Column(Boolean, default=True)
|
| 70 |
+
|
| 71 |
+
class DatabaseManager:
|
| 72 |
+
def __init__(self):
|
| 73 |
+
# SQLite persistent database for better data retention
|
| 74 |
+
self.engine = create_engine('sqlite:///stock_trading.db', echo=False)
|
| 75 |
+
try:
|
| 76 |
+
# Create all tables
|
| 77 |
+
Base.metadata.create_all(self.engine)
|
| 78 |
+
Session = sessionmaker(bind=self.engine)
|
| 79 |
+
self.session = Session()
|
| 80 |
+
|
| 81 |
+
# Verify tables were created
|
| 82 |
+
with self.engine.connect() as conn:
|
| 83 |
+
result = conn.execute(text("SELECT name FROM sqlite_master WHERE type='table'"))
|
| 84 |
+
tables = [row[0] for row in result]
|
| 85 |
+
print(f"Database tables available: {tables}")
|
| 86 |
+
|
| 87 |
+
except Exception as e:
|
| 88 |
+
print(f"Database initialization error: {str(e)}")
|
| 89 |
+
raise e
|
| 90 |
+
|
| 91 |
+
def save_stock_data(self, symbol, data_df):
|
| 92 |
+
"""Save stock OHLCV data to database"""
|
| 93 |
+
try:
|
| 94 |
+
for index, row in data_df.iterrows():
|
| 95 |
+
stock_record = StockData(
|
| 96 |
+
symbol=symbol,
|
| 97 |
+
date=index,
|
| 98 |
+
open_price=float(row['Open']),
|
| 99 |
+
high_price=float(row['High']),
|
| 100 |
+
low_price=float(row['Low']),
|
| 101 |
+
close_price=float(row['Close']),
|
| 102 |
+
volume=int(row['Volume'])
|
| 103 |
+
)
|
| 104 |
+
self.session.merge(stock_record)
|
| 105 |
+
self.session.commit()
|
| 106 |
+
return True
|
| 107 |
+
except Exception as e:
|
| 108 |
+
self.session.rollback()
|
| 109 |
+
print(f"Error saving stock data: {str(e)}")
|
| 110 |
+
return False
|
| 111 |
+
|
| 112 |
+
def save_technical_indicators(self, symbol, indicators_df):
|
| 113 |
+
"""Save technical indicators to database"""
|
| 114 |
+
try:
|
| 115 |
+
for index, row in indicators_df.iterrows():
|
| 116 |
+
indicator_record = TechnicalIndicators(
|
| 117 |
+
symbol=symbol,
|
| 118 |
+
date=index,
|
| 119 |
+
rsi=float(row.get('RSI', 0)) if pd.notna(row.get('RSI', 0)) else None,
|
| 120 |
+
macd=float(row.get('MACD', 0)) if pd.notna(row.get('MACD', 0)) else None,
|
| 121 |
+
macd_signal=float(row.get('MACD_signal', 0)) if pd.notna(row.get('MACD_signal', 0)) else None,
|
| 122 |
+
bb_upper=float(row.get('BB_upper', 0)) if pd.notna(row.get('BB_upper', 0)) else None,
|
| 123 |
+
bb_middle=float(row.get('BB_middle', 0)) if pd.notna(row.get('BB_middle', 0)) else None,
|
| 124 |
+
bb_lower=float(row.get('BB_lower', 0)) if pd.notna(row.get('BB_lower', 0)) else None,
|
| 125 |
+
ema_20=float(row.get('EMA_20', 0)) if pd.notna(row.get('EMA_20', 0)) else None,
|
| 126 |
+
sma_20=float(row.get('SMA_20', 0)) if pd.notna(row.get('SMA_20', 0)) else None,
|
| 127 |
+
sma_50=float(row.get('SMA_50', 0)) if pd.notna(row.get('SMA_50', 0)) else None,
|
| 128 |
+
stoch_k=float(row.get('Stoch_K', 0)) if pd.notna(row.get('Stoch_K', 0)) else None,
|
| 129 |
+
williams_r=float(row.get('Williams_R', 0)) if pd.notna(row.get('Williams_R', 0)) else None,
|
| 130 |
+
atr=float(row.get('ATR', 0)) if pd.notna(row.get('ATR', 0)) else None
|
| 131 |
+
)
|
| 132 |
+
self.session.merge(indicator_record)
|
| 133 |
+
self.session.commit()
|
| 134 |
+
return True
|
| 135 |
+
except Exception as e:
|
| 136 |
+
self.session.rollback()
|
| 137 |
+
print(f"Error saving technical indicators: {str(e)}")
|
| 138 |
+
return False
|
| 139 |
+
|
| 140 |
+
def save_trading_signal(self, symbol, signal, confidence, live_price, predicted_return=None, model_version="v1.0"):
|
| 141 |
+
"""Save trading signal to database"""
|
| 142 |
+
try:
|
| 143 |
+
signal_record = TradingSignals(
|
| 144 |
+
symbol=symbol,
|
| 145 |
+
signal_date=datetime.now(),
|
| 146 |
+
signal=signal,
|
| 147 |
+
confidence=confidence,
|
| 148 |
+
live_price=live_price,
|
| 149 |
+
predicted_return=predicted_return,
|
| 150 |
+
model_version=model_version
|
| 151 |
+
)
|
| 152 |
+
self.session.add(signal_record)
|
| 153 |
+
self.session.commit()
|
| 154 |
+
return True
|
| 155 |
+
except Exception as e:
|
| 156 |
+
self.session.rollback()
|
| 157 |
+
print(f"Error saving trading signal: {str(e)}")
|
| 158 |
+
return False
|
| 159 |
+
|
| 160 |
+
def save_model_metrics(self, symbol, model_type, accuracy, precision, recall, feature_count, training_samples):
|
| 161 |
+
"""Save model performance metrics to database"""
|
| 162 |
+
try:
|
| 163 |
+
# Deactivate old models for this symbol
|
| 164 |
+
old_models = self.session.query(ModelMetrics).filter_by(symbol=symbol, is_active=True).all()
|
| 165 |
+
for model in old_models:
|
| 166 |
+
model.is_active = False
|
| 167 |
+
|
| 168 |
+
# Add new model metrics
|
| 169 |
+
metrics_record = ModelMetrics(
|
| 170 |
+
symbol=symbol,
|
| 171 |
+
model_type=model_type,
|
| 172 |
+
accuracy=accuracy,
|
| 173 |
+
precision_score=precision,
|
| 174 |
+
recall_score=recall,
|
| 175 |
+
feature_count=feature_count,
|
| 176 |
+
training_samples=training_samples,
|
| 177 |
+
is_active=True
|
| 178 |
+
)
|
| 179 |
+
self.session.add(metrics_record)
|
| 180 |
+
self.session.commit()
|
| 181 |
+
return True
|
| 182 |
+
except Exception as e:
|
| 183 |
+
self.session.rollback()
|
| 184 |
+
print(f"Error saving model metrics: {str(e)}")
|
| 185 |
+
return False
|
| 186 |
+
|
| 187 |
+
def get_latest_stock_data(self, symbol, limit=100):
|
| 188 |
+
"""Get latest stock data from database"""
|
| 189 |
+
try:
|
| 190 |
+
records = self.session.query(StockData).filter_by(symbol=symbol)\
|
| 191 |
+
.order_by(StockData.date.desc()).limit(limit).all()
|
| 192 |
+
|
| 193 |
+
data = []
|
| 194 |
+
for record in records:
|
| 195 |
+
data.append({
|
| 196 |
+
'Date': record.date,
|
| 197 |
+
'Open': record.open_price,
|
| 198 |
+
'High': record.high_price,
|
| 199 |
+
'Low': record.low_price,
|
| 200 |
+
'Close': record.close_price,
|
| 201 |
+
'Volume': record.volume
|
| 202 |
+
})
|
| 203 |
+
|
| 204 |
+
return pd.DataFrame(data).set_index('Date').sort_index()
|
| 205 |
+
except Exception as e:
|
| 206 |
+
print(f"Error getting stock data: {str(e)}")
|
| 207 |
+
return pd.DataFrame()
|
| 208 |
+
|
| 209 |
+
def get_trading_signals_history(self, symbol, limit=50):
|
| 210 |
+
"""Get trading signals history for a symbol"""
|
| 211 |
+
try:
|
| 212 |
+
records = self.session.query(TradingSignals).filter_by(symbol=symbol)\
|
| 213 |
+
.order_by(TradingSignals.signal_date.desc()).limit(limit).all()
|
| 214 |
+
|
| 215 |
+
signals = []
|
| 216 |
+
for record in records:
|
| 217 |
+
signals.append({
|
| 218 |
+
'Date': record.signal_date,
|
| 219 |
+
'Signal': record.signal,
|
| 220 |
+
'Confidence': record.confidence,
|
| 221 |
+
'Live Price': record.live_price,
|
| 222 |
+
'Model Version': record.model_version
|
| 223 |
+
})
|
| 224 |
+
|
| 225 |
+
return pd.DataFrame(signals)
|
| 226 |
+
except Exception as e:
|
| 227 |
+
print(f"Error getting trading signals: {str(e)}")
|
| 228 |
+
return pd.DataFrame()
|
| 229 |
+
|
| 230 |
+
def get_model_performance(self, symbol):
|
| 231 |
+
"""Get current model performance metrics"""
|
| 232 |
+
try:
|
| 233 |
+
record = self.session.query(ModelMetrics).filter_by(symbol=symbol, is_active=True).first()
|
| 234 |
+
if record:
|
| 235 |
+
return {
|
| 236 |
+
'model_type': record.model_type,
|
| 237 |
+
'accuracy': record.accuracy,
|
| 238 |
+
'precision': record.precision_score,
|
| 239 |
+
'recall': record.recall_score,
|
| 240 |
+
'training_date': record.training_date,
|
| 241 |
+
'feature_count': record.feature_count,
|
| 242 |
+
'training_samples': record.training_samples
|
| 243 |
+
}
|
| 244 |
+
return None
|
| 245 |
+
except Exception as e:
|
| 246 |
+
print(f"Error getting model performance: {str(e)}")
|
| 247 |
+
return None
|
| 248 |
+
|
| 249 |
+
def get_database_stats(self):
|
| 250 |
+
"""Get database statistics"""
|
| 251 |
+
try:
|
| 252 |
+
stats = {
|
| 253 |
+
'total_stock_records': self.session.query(StockData).count(),
|
| 254 |
+
'total_indicators': self.session.query(TechnicalIndicators).count(),
|
| 255 |
+
'total_signals': self.session.query(TradingSignals).count(),
|
| 256 |
+
'active_models': self.session.query(ModelMetrics).filter_by(is_active=True).count(),
|
| 257 |
+
'symbols_tracked': len(self.session.query(StockData.symbol).distinct().all())
|
| 258 |
+
}
|
| 259 |
+
return stats
|
| 260 |
+
except Exception as e:
|
| 261 |
+
print(f"Error getting database stats: {str(e)}")
|
| 262 |
+
return {}
|
| 263 |
+
|
| 264 |
+
def close(self):
|
| 265 |
+
"""Close database connection"""
|
| 266 |
+
self.session.close()
|
database_console.py
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import pandas as pd
|
| 3 |
+
from database import DatabaseManager
|
| 4 |
+
from sqlalchemy import text
|
| 5 |
+
import plotly.express as px
|
| 6 |
+
import plotly.graph_objects as go
|
| 7 |
+
from datetime import datetime, timedelta
|
| 8 |
+
|
| 9 |
+
def database_console():
|
| 10 |
+
"""Database console interface for viewing and managing stock data"""
|
| 11 |
+
st.title("📊 Database Console - Stock Trading Data")
|
| 12 |
+
st.markdown("### SQLite Database Management")
|
| 13 |
+
|
| 14 |
+
# Show database file location
|
| 15 |
+
st.info("Database file: stock_trading.db (persistent storage)")
|
| 16 |
+
|
| 17 |
+
# Initialize database
|
| 18 |
+
try:
|
| 19 |
+
if 'console_db' not in st.session_state:
|
| 20 |
+
from database import DatabaseManager
|
| 21 |
+
st.session_state.console_db = DatabaseManager()
|
| 22 |
+
db = st.session_state.console_db
|
| 23 |
+
st.success("Database connection established")
|
| 24 |
+
|
| 25 |
+
# Show database file size if it exists
|
| 26 |
+
import os
|
| 27 |
+
if os.path.exists('stock_trading.db'):
|
| 28 |
+
size = os.path.getsize('stock_trading.db')
|
| 29 |
+
st.metric("Database Size", f"{size / 1024:.1f} KB")
|
| 30 |
+
|
| 31 |
+
except Exception as e:
|
| 32 |
+
st.error(f"Database connection failed: {str(e)}")
|
| 33 |
+
return
|
| 34 |
+
|
| 35 |
+
# Sidebar navigation
|
| 36 |
+
st.sidebar.header("Database Navigation")
|
| 37 |
+
section = st.sidebar.selectbox(
|
| 38 |
+
"Select Section:",
|
| 39 |
+
["Overview", "Stock Data", "Technical Indicators", "Trading Signals", "Model Metrics", "SQL Query"]
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
if section == "Overview":
|
| 43 |
+
show_database_overview(db)
|
| 44 |
+
elif section == "Stock Data":
|
| 45 |
+
show_stock_data(db)
|
| 46 |
+
elif section == "Technical Indicators":
|
| 47 |
+
show_technical_indicators(db)
|
| 48 |
+
elif section == "Trading Signals":
|
| 49 |
+
show_trading_signals(db)
|
| 50 |
+
elif section == "Model Metrics":
|
| 51 |
+
show_model_metrics(db)
|
| 52 |
+
elif section == "SQL Query":
|
| 53 |
+
show_sql_interface(db)
|
| 54 |
+
|
| 55 |
+
def show_database_overview(db):
|
| 56 |
+
"""Show database overview and statistics"""
|
| 57 |
+
st.header("Database Overview")
|
| 58 |
+
|
| 59 |
+
col1, col2, col3, col4 = st.columns(4)
|
| 60 |
+
|
| 61 |
+
try:
|
| 62 |
+
# Get table counts
|
| 63 |
+
stock_count = db.session.execute(text("SELECT COUNT(*) FROM stock_data")).scalar()
|
| 64 |
+
indicator_count = db.session.execute(text("SELECT COUNT(*) FROM technical_indicators")).scalar()
|
| 65 |
+
signal_count = db.session.execute(text("SELECT COUNT(*) FROM trading_signals")).scalar()
|
| 66 |
+
model_count = db.session.execute(text("SELECT COUNT(*) FROM model_metrics")).scalar()
|
| 67 |
+
|
| 68 |
+
with col1:
|
| 69 |
+
st.metric("Stock Records", stock_count)
|
| 70 |
+
with col2:
|
| 71 |
+
st.metric("Technical Indicators", indicator_count)
|
| 72 |
+
with col3:
|
| 73 |
+
st.metric("Trading Signals", signal_count)
|
| 74 |
+
with col4:
|
| 75 |
+
st.metric("Model Records", model_count)
|
| 76 |
+
|
| 77 |
+
except Exception as e:
|
| 78 |
+
st.warning(f"Error getting counts: {str(e)}")
|
| 79 |
+
# Initialize tables if they don't exist
|
| 80 |
+
if st.button("Initialize Database Tables"):
|
| 81 |
+
initialize_database_tables(db)
|
| 82 |
+
|
| 83 |
+
# Recent activity
|
| 84 |
+
st.subheader("Recent Activity")
|
| 85 |
+
try:
|
| 86 |
+
recent_signals = pd.read_sql("""
|
| 87 |
+
SELECT symbol, signal, confidence, live_price, created_at
|
| 88 |
+
FROM trading_signals
|
| 89 |
+
ORDER BY created_at DESC
|
| 90 |
+
LIMIT 10
|
| 91 |
+
""", db.engine)
|
| 92 |
+
|
| 93 |
+
if not recent_signals.empty:
|
| 94 |
+
st.dataframe(recent_signals)
|
| 95 |
+
else:
|
| 96 |
+
st.info("No recent trading signals found")
|
| 97 |
+
|
| 98 |
+
except Exception as e:
|
| 99 |
+
st.info("No trading signals table found yet")
|
| 100 |
+
|
| 101 |
+
def show_stock_data(db):
|
| 102 |
+
"""Show stock OHLCV data"""
|
| 103 |
+
st.header("Stock Data (OHLCV)")
|
| 104 |
+
|
| 105 |
+
# Stock selection
|
| 106 |
+
symbol = st.selectbox(
|
| 107 |
+
"Select Stock Symbol:",
|
| 108 |
+
["RELIANCE.NS", "TCS.NS", "INFY.NS", "HDFCBANK.NS", "ICICIBANK.NS"]
|
| 109 |
+
)
|
| 110 |
+
|
| 111 |
+
try:
|
| 112 |
+
# Get stock data
|
| 113 |
+
query = """
|
| 114 |
+
SELECT * FROM stock_data
|
| 115 |
+
WHERE symbol = :symbol
|
| 116 |
+
ORDER BY date DESC
|
| 117 |
+
LIMIT 100
|
| 118 |
+
"""
|
| 119 |
+
stock_data = pd.read_sql(query, db.engine, params={"symbol": symbol})
|
| 120 |
+
|
| 121 |
+
if not stock_data.empty:
|
| 122 |
+
st.dataframe(stock_data)
|
| 123 |
+
|
| 124 |
+
# Create price chart
|
| 125 |
+
fig = go.Figure()
|
| 126 |
+
fig.add_trace(go.Candlestick(
|
| 127 |
+
x=stock_data['date'],
|
| 128 |
+
open=stock_data['open_price'],
|
| 129 |
+
high=stock_data['high_price'],
|
| 130 |
+
low=stock_data['low_price'],
|
| 131 |
+
close=stock_data['close_price'],
|
| 132 |
+
name=symbol
|
| 133 |
+
))
|
| 134 |
+
fig.update_layout(title=f"{symbol} - Price Chart", xaxis_title="Date", yaxis_title="Price (₹)")
|
| 135 |
+
st.plotly_chart(fig, use_container_width=True)
|
| 136 |
+
|
| 137 |
+
else:
|
| 138 |
+
st.info(f"No stock data found for {symbol}")
|
| 139 |
+
if st.button(f"Load {symbol} Data"):
|
| 140 |
+
load_sample_data(db, symbol)
|
| 141 |
+
|
| 142 |
+
except Exception as e:
|
| 143 |
+
st.error(f"Error loading stock data: {str(e)}")
|
| 144 |
+
|
| 145 |
+
def show_technical_indicators(db):
|
| 146 |
+
"""Show technical indicators data"""
|
| 147 |
+
st.header("Technical Indicators")
|
| 148 |
+
|
| 149 |
+
symbol = st.selectbox(
|
| 150 |
+
"Select Stock:",
|
| 151 |
+
["RELIANCE.NS", "TCS.NS", "INFY.NS", "HDFCBANK.NS", "ICICIBANK.NS"],
|
| 152 |
+
key="tech_symbol"
|
| 153 |
+
)
|
| 154 |
+
|
| 155 |
+
try:
|
| 156 |
+
query = """
|
| 157 |
+
SELECT * FROM technical_indicators
|
| 158 |
+
WHERE symbol = :symbol
|
| 159 |
+
ORDER BY date DESC
|
| 160 |
+
LIMIT 50
|
| 161 |
+
"""
|
| 162 |
+
tech_data = pd.read_sql(query, db.engine, params={"symbol": symbol})
|
| 163 |
+
|
| 164 |
+
if not tech_data.empty:
|
| 165 |
+
st.dataframe(tech_data)
|
| 166 |
+
|
| 167 |
+
# Create indicators chart
|
| 168 |
+
col1, col2 = st.columns(2)
|
| 169 |
+
|
| 170 |
+
with col1:
|
| 171 |
+
if 'rsi' in tech_data.columns:
|
| 172 |
+
fig_rsi = px.line(tech_data, x='date', y='rsi', title='RSI')
|
| 173 |
+
st.plotly_chart(fig_rsi, use_container_width=True)
|
| 174 |
+
|
| 175 |
+
with col2:
|
| 176 |
+
if 'macd' in tech_data.columns:
|
| 177 |
+
fig_macd = px.line(tech_data, x='date', y='macd', title='MACD')
|
| 178 |
+
st.plotly_chart(fig_macd, use_container_width=True)
|
| 179 |
+
|
| 180 |
+
else:
|
| 181 |
+
st.info(f"No technical indicators found for {symbol}")
|
| 182 |
+
|
| 183 |
+
except Exception as e:
|
| 184 |
+
st.error(f"Error loading technical indicators: {str(e)}")
|
| 185 |
+
|
| 186 |
+
def show_trading_signals(db):
|
| 187 |
+
"""Show trading signals"""
|
| 188 |
+
st.header("Trading Signals")
|
| 189 |
+
|
| 190 |
+
try:
|
| 191 |
+
# Get all signals
|
| 192 |
+
signals_data = pd.read_sql("""
|
| 193 |
+
SELECT * FROM trading_signals
|
| 194 |
+
ORDER BY signal_date DESC
|
| 195 |
+
LIMIT 100
|
| 196 |
+
""", db.engine)
|
| 197 |
+
|
| 198 |
+
if not signals_data.empty:
|
| 199 |
+
st.dataframe(signals_data)
|
| 200 |
+
|
| 201 |
+
# Signal distribution
|
| 202 |
+
col1, col2 = st.columns(2)
|
| 203 |
+
|
| 204 |
+
with col1:
|
| 205 |
+
signal_counts = signals_data['signal'].value_counts()
|
| 206 |
+
fig_pie = px.pie(values=signal_counts.values, names=signal_counts.index,
|
| 207 |
+
title="Signal Distribution")
|
| 208 |
+
st.plotly_chart(fig_pie, use_container_width=True)
|
| 209 |
+
|
| 210 |
+
with col2:
|
| 211 |
+
fig_conf = px.histogram(signals_data, x='confidence', title="Confidence Distribution")
|
| 212 |
+
st.plotly_chart(fig_conf, use_container_width=True)
|
| 213 |
+
|
| 214 |
+
else:
|
| 215 |
+
st.info("No trading signals found")
|
| 216 |
+
|
| 217 |
+
except Exception as e:
|
| 218 |
+
st.error(f"Error loading trading signals: {str(e)}")
|
| 219 |
+
|
| 220 |
+
def show_model_metrics(db):
|
| 221 |
+
"""Show model performance metrics"""
|
| 222 |
+
st.header("Model Performance Metrics")
|
| 223 |
+
|
| 224 |
+
try:
|
| 225 |
+
metrics_data = pd.read_sql("""
|
| 226 |
+
SELECT * FROM model_metrics
|
| 227 |
+
ORDER BY training_date DESC
|
| 228 |
+
""", db.engine)
|
| 229 |
+
|
| 230 |
+
if not metrics_data.empty:
|
| 231 |
+
st.dataframe(metrics_data)
|
| 232 |
+
|
| 233 |
+
# Performance charts
|
| 234 |
+
col1, col2 = st.columns(2)
|
| 235 |
+
|
| 236 |
+
with col1:
|
| 237 |
+
fig_acc = px.bar(metrics_data, x='symbol', y='accuracy',
|
| 238 |
+
title="Model Accuracy by Symbol")
|
| 239 |
+
st.plotly_chart(fig_acc, use_container_width=True)
|
| 240 |
+
|
| 241 |
+
with col2:
|
| 242 |
+
fig_prec = px.bar(metrics_data, x='symbol', y='precision_score',
|
| 243 |
+
title="Model Precision by Symbol")
|
| 244 |
+
st.plotly_chart(fig_prec, use_container_width=True)
|
| 245 |
+
|
| 246 |
+
else:
|
| 247 |
+
st.info("No model metrics found")
|
| 248 |
+
|
| 249 |
+
except Exception as e:
|
| 250 |
+
st.error(f"Error loading model metrics: {str(e)}")
|
| 251 |
+
|
| 252 |
+
def show_sql_interface(db):
|
| 253 |
+
"""SQL query interface"""
|
| 254 |
+
st.header("SQL Query Interface")
|
| 255 |
+
|
| 256 |
+
# Predefined queries
|
| 257 |
+
st.subheader("Quick Queries")
|
| 258 |
+
col1, col2, col3 = st.columns(3)
|
| 259 |
+
|
| 260 |
+
with col1:
|
| 261 |
+
if st.button("Show All Tables"):
|
| 262 |
+
try:
|
| 263 |
+
tables = db.engine.execute(text("SELECT name FROM sqlite_master WHERE type='table'")).fetchall()
|
| 264 |
+
st.write("Tables:", [table[0] for table in tables])
|
| 265 |
+
except Exception as e:
|
| 266 |
+
st.error(f"Error: {str(e)}")
|
| 267 |
+
|
| 268 |
+
with col2:
|
| 269 |
+
if st.button("Latest Signals"):
|
| 270 |
+
try:
|
| 271 |
+
result = pd.read_sql("SELECT * FROM trading_signals ORDER BY created_at DESC LIMIT 5", db.engine)
|
| 272 |
+
st.dataframe(result)
|
| 273 |
+
except Exception as e:
|
| 274 |
+
st.error(f"Error: {str(e)}")
|
| 275 |
+
|
| 276 |
+
with col3:
|
| 277 |
+
if st.button("Stock Summary"):
|
| 278 |
+
try:
|
| 279 |
+
result = pd.read_sql("""
|
| 280 |
+
SELECT symbol, COUNT(*) as records,
|
| 281 |
+
MIN(date) as first_date, MAX(date) as last_date
|
| 282 |
+
FROM stock_data
|
| 283 |
+
GROUP BY symbol
|
| 284 |
+
""", db.engine)
|
| 285 |
+
st.dataframe(result)
|
| 286 |
+
except Exception as e:
|
| 287 |
+
st.error(f"Error: {str(e)}")
|
| 288 |
+
|
| 289 |
+
# Custom SQL query
|
| 290 |
+
st.subheader("Custom SQL Query")
|
| 291 |
+
sql_query = st.text_area(
|
| 292 |
+
"Enter SQL Query:",
|
| 293 |
+
value="SELECT * FROM trading_signals LIMIT 10;",
|
| 294 |
+
height=100
|
| 295 |
+
)
|
| 296 |
+
|
| 297 |
+
if st.button("Execute Query"):
|
| 298 |
+
try:
|
| 299 |
+
if sql_query.strip():
|
| 300 |
+
result = pd.read_sql(sql_query, db.engine)
|
| 301 |
+
st.dataframe(result)
|
| 302 |
+
st.success(f"Query executed successfully. {len(result)} rows returned.")
|
| 303 |
+
except Exception as e:
|
| 304 |
+
st.error(f"Query error: {str(e)}")
|
| 305 |
+
|
| 306 |
+
def initialize_database_tables(db):
|
| 307 |
+
"""Initialize database tables with sample data"""
|
| 308 |
+
try:
|
| 309 |
+
from database import Base
|
| 310 |
+
Base.metadata.create_all(db.engine)
|
| 311 |
+
st.success("Database tables initialized successfully")
|
| 312 |
+
st.rerun()
|
| 313 |
+
except Exception as e:
|
| 314 |
+
st.error(f"Error initializing tables: {str(e)}")
|
| 315 |
+
|
| 316 |
+
def load_sample_data(db, symbol):
|
| 317 |
+
"""Load sample data for testing"""
|
| 318 |
+
try:
|
| 319 |
+
import yfinance as yf
|
| 320 |
+
from technical_indicators import TechnicalIndicators
|
| 321 |
+
|
| 322 |
+
# Download stock data
|
| 323 |
+
ticker = yf.Ticker(symbol)
|
| 324 |
+
data = ticker.history(period="1mo")
|
| 325 |
+
|
| 326 |
+
if not data.empty:
|
| 327 |
+
# Save stock data
|
| 328 |
+
db.save_stock_data(symbol, data)
|
| 329 |
+
|
| 330 |
+
# Calculate and save technical indicators
|
| 331 |
+
tech = TechnicalIndicators()
|
| 332 |
+
data_with_indicators = tech.calculate_all_indicators(data)
|
| 333 |
+
db.save_technical_indicators(symbol, data_with_indicators)
|
| 334 |
+
|
| 335 |
+
st.success(f"Sample data loaded for {symbol}")
|
| 336 |
+
st.rerun()
|
| 337 |
+
else:
|
| 338 |
+
st.error(f"Could not fetch data for {symbol}")
|
| 339 |
+
|
| 340 |
+
except Exception as e:
|
| 341 |
+
st.error(f"Error loading sample data: {str(e)}")
|
| 342 |
+
|
| 343 |
+
if __name__ == "__main__":
|
| 344 |
+
database_console()
|
db_console.py
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import pandas as pd
|
| 3 |
+
import sqlite3
|
| 4 |
+
import plotly.express as px
|
| 5 |
+
import plotly.graph_objects as go
|
| 6 |
+
import os
|
| 7 |
+
|
| 8 |
+
def main():
|
| 9 |
+
st.set_page_config(page_title="Stock Trading Database Console", layout="wide")
|
| 10 |
+
|
| 11 |
+
st.title("📊 Stock Trading Database Console")
|
| 12 |
+
st.markdown("### SQLite Database Management & Analytics")
|
| 13 |
+
|
| 14 |
+
# Database file info
|
| 15 |
+
db_file = "stock_trading.db"
|
| 16 |
+
if os.path.exists(db_file):
|
| 17 |
+
size = os.path.getsize(db_file)
|
| 18 |
+
st.success(f"Database: {db_file} ({size/1024:.1f} KB)")
|
| 19 |
+
else:
|
| 20 |
+
st.warning(f"Database file {db_file} not found. Data will be created when you use the main app.")
|
| 21 |
+
return
|
| 22 |
+
|
| 23 |
+
# Connect to database
|
| 24 |
+
try:
|
| 25 |
+
conn = sqlite3.connect(db_file)
|
| 26 |
+
st.success("Database connection established")
|
| 27 |
+
except Exception as e:
|
| 28 |
+
st.error(f"Database connection failed: {str(e)}")
|
| 29 |
+
return
|
| 30 |
+
|
| 31 |
+
# Sidebar navigation
|
| 32 |
+
st.sidebar.header("Database Navigation")
|
| 33 |
+
section = st.sidebar.selectbox(
|
| 34 |
+
"Select Section:",
|
| 35 |
+
["Overview", "Tables", "Stock Data", "Trading Signals", "Model Metrics", "SQL Query"]
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
if section == "Overview":
|
| 39 |
+
show_overview(conn)
|
| 40 |
+
elif section == "Tables":
|
| 41 |
+
show_tables(conn)
|
| 42 |
+
elif section == "Stock Data":
|
| 43 |
+
show_stock_data(conn)
|
| 44 |
+
elif section == "Trading Signals":
|
| 45 |
+
show_trading_signals(conn)
|
| 46 |
+
elif section == "Model Metrics":
|
| 47 |
+
show_model_metrics(conn)
|
| 48 |
+
elif section == "SQL Query":
|
| 49 |
+
show_sql_interface(conn)
|
| 50 |
+
|
| 51 |
+
conn.close()
|
| 52 |
+
|
| 53 |
+
def show_overview(conn):
|
| 54 |
+
"""Show database overview"""
|
| 55 |
+
st.header("Database Overview")
|
| 56 |
+
|
| 57 |
+
# Get table information
|
| 58 |
+
tables_query = "SELECT name FROM sqlite_master WHERE type='table'"
|
| 59 |
+
tables = pd.read_sql(tables_query, conn)
|
| 60 |
+
|
| 61 |
+
if not tables.empty:
|
| 62 |
+
st.subheader("Available Tables")
|
| 63 |
+
st.dataframe(tables)
|
| 64 |
+
|
| 65 |
+
# Table statistics
|
| 66 |
+
col1, col2, col3, col4 = st.columns(4)
|
| 67 |
+
|
| 68 |
+
try:
|
| 69 |
+
stock_count = pd.read_sql("SELECT COUNT(*) as count FROM stock_data", conn).iloc[0]['count']
|
| 70 |
+
with col1:
|
| 71 |
+
st.metric("Stock Records", stock_count)
|
| 72 |
+
except:
|
| 73 |
+
with col1:
|
| 74 |
+
st.metric("Stock Records", 0)
|
| 75 |
+
|
| 76 |
+
try:
|
| 77 |
+
signals_count = pd.read_sql("SELECT COUNT(*) as count FROM trading_signals", conn).iloc[0]['count']
|
| 78 |
+
with col2:
|
| 79 |
+
st.metric("Trading Signals", signals_count)
|
| 80 |
+
except:
|
| 81 |
+
with col2:
|
| 82 |
+
st.metric("Trading Signals", 0)
|
| 83 |
+
|
| 84 |
+
try:
|
| 85 |
+
indicators_count = pd.read_sql("SELECT COUNT(*) as count FROM technical_indicators", conn).iloc[0]['count']
|
| 86 |
+
with col3:
|
| 87 |
+
st.metric("Technical Indicators", indicators_count)
|
| 88 |
+
except:
|
| 89 |
+
with col3:
|
| 90 |
+
st.metric("Technical Indicators", 0)
|
| 91 |
+
|
| 92 |
+
try:
|
| 93 |
+
models_count = pd.read_sql("SELECT COUNT(*) as count FROM model_metrics", conn).iloc[0]['count']
|
| 94 |
+
with col4:
|
| 95 |
+
st.metric("Model Metrics", models_count)
|
| 96 |
+
except:
|
| 97 |
+
with col4:
|
| 98 |
+
st.metric("Model Metrics", 0)
|
| 99 |
+
else:
|
| 100 |
+
st.info("No tables found. Use the main app to generate data.")
|
| 101 |
+
|
| 102 |
+
def show_tables(conn):
|
| 103 |
+
"""Show table schemas"""
|
| 104 |
+
st.header("Table Schemas")
|
| 105 |
+
|
| 106 |
+
# Get all tables
|
| 107 |
+
tables_query = "SELECT name FROM sqlite_master WHERE type='table'"
|
| 108 |
+
tables = pd.read_sql(tables_query, conn)
|
| 109 |
+
|
| 110 |
+
for table_name in tables['name']:
|
| 111 |
+
st.subheader(f"Table: {table_name}")
|
| 112 |
+
|
| 113 |
+
# Get table schema
|
| 114 |
+
schema_query = f"PRAGMA table_info({table_name})"
|
| 115 |
+
schema = pd.read_sql(schema_query, conn)
|
| 116 |
+
st.dataframe(schema)
|
| 117 |
+
|
| 118 |
+
# Get sample data
|
| 119 |
+
try:
|
| 120 |
+
sample_query = f"SELECT * FROM {table_name} LIMIT 3"
|
| 121 |
+
sample = pd.read_sql(sample_query, conn)
|
| 122 |
+
if not sample.empty:
|
| 123 |
+
st.write("Sample Data:")
|
| 124 |
+
st.dataframe(sample)
|
| 125 |
+
except:
|
| 126 |
+
st.write("No sample data available")
|
| 127 |
+
|
| 128 |
+
st.markdown("---")
|
| 129 |
+
|
| 130 |
+
def show_stock_data(conn):
|
| 131 |
+
"""Show stock OHLCV data"""
|
| 132 |
+
st.header("Stock Data Analysis")
|
| 133 |
+
|
| 134 |
+
try:
|
| 135 |
+
# Get unique symbols
|
| 136 |
+
symbols_query = "SELECT DISTINCT symbol FROM stock_data ORDER BY symbol"
|
| 137 |
+
symbols = pd.read_sql(symbols_query, conn)
|
| 138 |
+
|
| 139 |
+
if not symbols.empty:
|
| 140 |
+
selected_symbol = st.selectbox("Select Stock Symbol:", symbols['symbol'].tolist())
|
| 141 |
+
|
| 142 |
+
# Get data for selected symbol
|
| 143 |
+
data_query = f"""
|
| 144 |
+
SELECT * FROM stock_data
|
| 145 |
+
WHERE symbol = '{selected_symbol}'
|
| 146 |
+
ORDER BY date DESC
|
| 147 |
+
LIMIT 100
|
| 148 |
+
"""
|
| 149 |
+
stock_data = pd.read_sql(data_query, conn)
|
| 150 |
+
|
| 151 |
+
if not stock_data.empty:
|
| 152 |
+
st.dataframe(stock_data)
|
| 153 |
+
|
| 154 |
+
# Create candlestick chart
|
| 155 |
+
fig = go.Figure(data=go.Candlestick(
|
| 156 |
+
x=stock_data['date'],
|
| 157 |
+
open=stock_data['open_price'],
|
| 158 |
+
high=stock_data['high_price'],
|
| 159 |
+
low=stock_data['low_price'],
|
| 160 |
+
close=stock_data['close_price']
|
| 161 |
+
))
|
| 162 |
+
fig.update_layout(title=f"{selected_symbol} - Candlestick Chart")
|
| 163 |
+
st.plotly_chart(fig, use_container_width=True)
|
| 164 |
+
|
| 165 |
+
# Volume chart
|
| 166 |
+
fig_vol = px.bar(stock_data, x='date', y='volume', title=f"{selected_symbol} - Volume")
|
| 167 |
+
st.plotly_chart(fig_vol, use_container_width=True)
|
| 168 |
+
else:
|
| 169 |
+
st.info(f"No data found for {selected_symbol}")
|
| 170 |
+
else:
|
| 171 |
+
st.info("No stock data available. Train a model in the main app to generate data.")
|
| 172 |
+
except Exception as e:
|
| 173 |
+
st.error(f"Error loading stock data: {str(e)}")
|
| 174 |
+
|
| 175 |
+
def show_trading_signals(conn):
|
| 176 |
+
"""Show trading signals"""
|
| 177 |
+
st.header("Trading Signals Analysis")
|
| 178 |
+
|
| 179 |
+
try:
|
| 180 |
+
signals_query = "SELECT * FROM trading_signals ORDER BY signal_date DESC LIMIT 100"
|
| 181 |
+
signals = pd.read_sql(signals_query, conn)
|
| 182 |
+
|
| 183 |
+
if not signals.empty:
|
| 184 |
+
st.dataframe(signals)
|
| 185 |
+
|
| 186 |
+
# Signal distribution
|
| 187 |
+
col1, col2 = st.columns(2)
|
| 188 |
+
|
| 189 |
+
with col1:
|
| 190 |
+
signal_counts = signals['signal'].value_counts()
|
| 191 |
+
fig_pie = px.pie(values=signal_counts.values, names=signal_counts.index,
|
| 192 |
+
title="Signal Distribution")
|
| 193 |
+
st.plotly_chart(fig_pie, use_container_width=True)
|
| 194 |
+
|
| 195 |
+
with col2:
|
| 196 |
+
fig_conf = px.histogram(signals, x='confidence', title="Confidence Distribution")
|
| 197 |
+
st.plotly_chart(fig_conf, use_container_width=True)
|
| 198 |
+
|
| 199 |
+
# Signals by symbol
|
| 200 |
+
if 'symbol' in signals.columns:
|
| 201 |
+
symbol_signals = signals.groupby('symbol')['signal'].count().reset_index()
|
| 202 |
+
fig_bar = px.bar(symbol_signals, x='symbol', y='signal', title="Signals by Symbol")
|
| 203 |
+
st.plotly_chart(fig_bar, use_container_width=True)
|
| 204 |
+
else:
|
| 205 |
+
st.info("No trading signals found. Generate signals in the main app.")
|
| 206 |
+
except Exception as e:
|
| 207 |
+
st.error(f"Error loading trading signals: {str(e)}")
|
| 208 |
+
|
| 209 |
+
def show_model_metrics(conn):
|
| 210 |
+
"""Show model performance metrics"""
|
| 211 |
+
st.header("Model Performance Metrics")
|
| 212 |
+
|
| 213 |
+
try:
|
| 214 |
+
metrics_query = "SELECT * FROM model_metrics ORDER BY training_date DESC"
|
| 215 |
+
metrics = pd.read_sql(metrics_query, conn)
|
| 216 |
+
|
| 217 |
+
if not metrics.empty:
|
| 218 |
+
st.dataframe(metrics)
|
| 219 |
+
|
| 220 |
+
# Performance charts
|
| 221 |
+
col1, col2 = st.columns(2)
|
| 222 |
+
|
| 223 |
+
with col1:
|
| 224 |
+
fig_acc = px.bar(metrics, x='symbol', y='accuracy', title="Model Accuracy")
|
| 225 |
+
st.plotly_chart(fig_acc, use_container_width=True)
|
| 226 |
+
|
| 227 |
+
with col2:
|
| 228 |
+
fig_prec = px.bar(metrics, x='symbol', y='precision_score', title="Model Precision")
|
| 229 |
+
st.plotly_chart(fig_prec, use_container_width=True)
|
| 230 |
+
else:
|
| 231 |
+
st.info("No model metrics found. Train models in the main app.")
|
| 232 |
+
except Exception as e:
|
| 233 |
+
st.error(f"Error loading model metrics: {str(e)}")
|
| 234 |
+
|
| 235 |
+
def show_sql_interface(conn):
|
| 236 |
+
"""SQL query interface"""
|
| 237 |
+
st.header("SQL Query Interface")
|
| 238 |
+
|
| 239 |
+
# Quick queries
|
| 240 |
+
st.subheader("Quick Queries")
|
| 241 |
+
col1, col2, col3 = st.columns(3)
|
| 242 |
+
|
| 243 |
+
with col1:
|
| 244 |
+
if st.button("Show All Tables"):
|
| 245 |
+
query = "SELECT name FROM sqlite_master WHERE type='table'"
|
| 246 |
+
result = pd.read_sql(query, conn)
|
| 247 |
+
st.dataframe(result)
|
| 248 |
+
|
| 249 |
+
with col2:
|
| 250 |
+
if st.button("Latest Signals"):
|
| 251 |
+
try:
|
| 252 |
+
query = "SELECT * FROM trading_signals ORDER BY created_at DESC LIMIT 10"
|
| 253 |
+
result = pd.read_sql(query, conn)
|
| 254 |
+
st.dataframe(result)
|
| 255 |
+
except Exception as e:
|
| 256 |
+
st.error(f"Error: {str(e)}")
|
| 257 |
+
|
| 258 |
+
with col3:
|
| 259 |
+
if st.button("Stock Summary"):
|
| 260 |
+
try:
|
| 261 |
+
query = """
|
| 262 |
+
SELECT symbol, COUNT(*) as records,
|
| 263 |
+
MIN(date) as first_date, MAX(date) as last_date
|
| 264 |
+
FROM stock_data
|
| 265 |
+
GROUP BY symbol
|
| 266 |
+
"""
|
| 267 |
+
result = pd.read_sql(query, conn)
|
| 268 |
+
st.dataframe(result)
|
| 269 |
+
except Exception as e:
|
| 270 |
+
st.error(f"Error: {str(e)}")
|
| 271 |
+
|
| 272 |
+
# Custom SQL
|
| 273 |
+
st.subheader("Custom SQL Query")
|
| 274 |
+
sql_query = st.text_area(
|
| 275 |
+
"Enter SQL Query:",
|
| 276 |
+
value="SELECT * FROM sqlite_master WHERE type='table';",
|
| 277 |
+
height=100
|
| 278 |
+
)
|
| 279 |
+
|
| 280 |
+
if st.button("Execute Query"):
|
| 281 |
+
try:
|
| 282 |
+
if sql_query.strip():
|
| 283 |
+
result = pd.read_sql(sql_query, conn)
|
| 284 |
+
st.dataframe(result)
|
| 285 |
+
st.success(f"Query executed successfully. {len(result)} rows returned.")
|
| 286 |
+
except Exception as e:
|
| 287 |
+
st.error(f"Query error: {str(e)}")
|
| 288 |
+
|
| 289 |
+
if __name__ == "__main__":
|
| 290 |
+
main()
|
model_trainer.py
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
import numpy as np
|
| 3 |
+
from sklearn.ensemble import RandomForestClassifier
|
| 4 |
+
from sklearn.model_selection import train_test_split, cross_val_score, GridSearchCV
|
| 5 |
+
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score, precision_score, recall_score
|
| 6 |
+
from sklearn.preprocessing import StandardScaler
|
| 7 |
+
import xgboost as xgb
|
| 8 |
+
import warnings
|
| 9 |
+
warnings.filterwarnings('ignore')
|
| 10 |
+
|
| 11 |
+
class ModelTrainer:
|
| 12 |
+
def __init__(self):
|
| 13 |
+
self.scaler = StandardScaler()
|
| 14 |
+
|
| 15 |
+
def train_model(self, features, labels, model_type='random_forest'):
|
| 16 |
+
"""Train machine learning model for trading signals"""
|
| 17 |
+
try:
|
| 18 |
+
if len(features) == 0 or len(labels) == 0:
|
| 19 |
+
raise ValueError("No data available for training")
|
| 20 |
+
|
| 21 |
+
# Handle infinite and NaN values
|
| 22 |
+
features = features.replace([np.inf, -np.inf], np.nan)
|
| 23 |
+
features = features.fillna(features.mean())
|
| 24 |
+
|
| 25 |
+
# Split data
|
| 26 |
+
X_train, X_test, y_train, y_test = train_test_split(
|
| 27 |
+
features, labels, test_size=0.2, random_state=42, stratify=labels
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
# Scale features
|
| 31 |
+
X_train_scaled = self.scaler.fit_transform(X_train)
|
| 32 |
+
X_test_scaled = self.scaler.transform(X_test)
|
| 33 |
+
|
| 34 |
+
# Train model
|
| 35 |
+
if model_type == 'random_forest':
|
| 36 |
+
model = self.train_random_forest(X_train_scaled, y_train)
|
| 37 |
+
elif model_type == 'xgboost':
|
| 38 |
+
model = self.train_xgboost(X_train_scaled, y_train)
|
| 39 |
+
else:
|
| 40 |
+
raise ValueError(f"Unsupported model type: {model_type}")
|
| 41 |
+
|
| 42 |
+
# Evaluate model
|
| 43 |
+
metrics = self.evaluate_model(model, X_test_scaled, y_test)
|
| 44 |
+
|
| 45 |
+
# Feature importance
|
| 46 |
+
feature_importance = self.get_feature_importance(model, features.columns)
|
| 47 |
+
|
| 48 |
+
# Create final model pipeline that includes scaling
|
| 49 |
+
final_model = ModelPipeline(self.scaler, model)
|
| 50 |
+
|
| 51 |
+
return final_model, features.columns.tolist(), metrics
|
| 52 |
+
|
| 53 |
+
except Exception as e:
|
| 54 |
+
print(f"Error training model: {str(e)}")
|
| 55 |
+
raise e
|
| 56 |
+
|
| 57 |
+
def train_random_forest(self, X_train, y_train):
|
| 58 |
+
"""Train Random Forest model with hyperparameter tuning"""
|
| 59 |
+
try:
|
| 60 |
+
# Define parameter grid for tuning
|
| 61 |
+
param_grid = {
|
| 62 |
+
'n_estimators': [100, 200],
|
| 63 |
+
'max_depth': [10, 15, None],
|
| 64 |
+
'min_samples_split': [2, 5],
|
| 65 |
+
'min_samples_leaf': [1, 2]
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
# Create base model
|
| 69 |
+
rf = RandomForestClassifier(random_state=42, class_weight='balanced')
|
| 70 |
+
|
| 71 |
+
# Grid search with cross-validation
|
| 72 |
+
grid_search = GridSearchCV(
|
| 73 |
+
rf, param_grid, cv=5, scoring='accuracy', n_jobs=-1
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
grid_search.fit(X_train, y_train)
|
| 77 |
+
|
| 78 |
+
return grid_search.best_estimator_
|
| 79 |
+
|
| 80 |
+
except Exception as e:
|
| 81 |
+
print(f"Error training Random Forest: {str(e)}")
|
| 82 |
+
# Fallback to basic model
|
| 83 |
+
return RandomForestClassifier(
|
| 84 |
+
n_estimators=100, random_state=42, class_weight='balanced'
|
| 85 |
+
).fit(X_train, y_train)
|
| 86 |
+
|
| 87 |
+
def train_xgboost(self, X_train, y_train):
|
| 88 |
+
"""Train XGBoost model"""
|
| 89 |
+
try:
|
| 90 |
+
# Convert labels to ensure compatibility
|
| 91 |
+
label_mapping = {-1: 0, 0: 1, 1: 2}
|
| 92 |
+
y_train_mapped = np.array([label_mapping[label] for label in y_train])
|
| 93 |
+
|
| 94 |
+
model = xgb.XGBClassifier(
|
| 95 |
+
n_estimators=100,
|
| 96 |
+
max_depth=6,
|
| 97 |
+
learning_rate=0.1,
|
| 98 |
+
random_state=42,
|
| 99 |
+
eval_metric='mlogloss'
|
| 100 |
+
)
|
| 101 |
+
|
| 102 |
+
model.fit(X_train, y_train_mapped)
|
| 103 |
+
|
| 104 |
+
# Wrap model to handle label mapping
|
| 105 |
+
return XGBoostWrapper(model, label_mapping)
|
| 106 |
+
|
| 107 |
+
except Exception as e:
|
| 108 |
+
print(f"Error training XGBoost: {str(e)}")
|
| 109 |
+
raise e
|
| 110 |
+
|
| 111 |
+
def evaluate_model(self, model, X_test, y_test):
|
| 112 |
+
"""Evaluate model performance"""
|
| 113 |
+
try:
|
| 114 |
+
y_pred = model.predict(X_test)
|
| 115 |
+
|
| 116 |
+
# Calculate metrics
|
| 117 |
+
accuracy = accuracy_score(y_test, y_pred)
|
| 118 |
+
precision = precision_score(y_test, y_pred, average='weighted', zero_division=0)
|
| 119 |
+
recall = recall_score(y_test, y_pred, average='weighted', zero_division=0)
|
| 120 |
+
|
| 121 |
+
metrics = {
|
| 122 |
+
'accuracy': accuracy,
|
| 123 |
+
'precision': precision,
|
| 124 |
+
'recall': recall,
|
| 125 |
+
'classification_report': classification_report(y_test, y_pred, zero_division=0),
|
| 126 |
+
'confusion_matrix': confusion_matrix(y_test, y_pred).tolist()
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
return metrics
|
| 130 |
+
|
| 131 |
+
except Exception as e:
|
| 132 |
+
print(f"Error evaluating model: {str(e)}")
|
| 133 |
+
return {'accuracy': 0.0, 'precision': 0.0, 'recall': 0.0}
|
| 134 |
+
|
| 135 |
+
def get_feature_importance(self, model, feature_names):
|
| 136 |
+
"""Get feature importance from trained model"""
|
| 137 |
+
try:
|
| 138 |
+
if hasattr(model, 'feature_importances_'):
|
| 139 |
+
importance = model.feature_importances_
|
| 140 |
+
elif hasattr(model, 'model') and hasattr(model.model, 'feature_importances_'):
|
| 141 |
+
importance = model.model.feature_importances_
|
| 142 |
+
else:
|
| 143 |
+
return {}
|
| 144 |
+
|
| 145 |
+
feature_importance = dict(zip(feature_names, importance))
|
| 146 |
+
|
| 147 |
+
# Sort by importance
|
| 148 |
+
sorted_importance = dict(sorted(feature_importance.items(),
|
| 149 |
+
key=lambda x: x[1], reverse=True))
|
| 150 |
+
|
| 151 |
+
return sorted_importance
|
| 152 |
+
|
| 153 |
+
except Exception as e:
|
| 154 |
+
print(f"Error getting feature importance: {str(e)}")
|
| 155 |
+
return {}
|
| 156 |
+
|
| 157 |
+
class ModelPipeline:
|
| 158 |
+
"""Pipeline that combines scaling and model prediction"""
|
| 159 |
+
def __init__(self, scaler, model):
|
| 160 |
+
self.scaler = scaler
|
| 161 |
+
self.model = model
|
| 162 |
+
|
| 163 |
+
def predict(self, X):
|
| 164 |
+
"""Make predictions with scaling"""
|
| 165 |
+
X_scaled = self.scaler.transform(X)
|
| 166 |
+
return self.model.predict(X_scaled)
|
| 167 |
+
|
| 168 |
+
def predict_proba(self, X):
|
| 169 |
+
"""Make probability predictions with scaling"""
|
| 170 |
+
X_scaled = self.scaler.transform(X)
|
| 171 |
+
return self.model.predict_proba(X_scaled)
|
| 172 |
+
|
| 173 |
+
class XGBoostWrapper:
|
| 174 |
+
"""Wrapper for XGBoost to handle label mapping"""
|
| 175 |
+
def __init__(self, model, label_mapping):
|
| 176 |
+
self.model = model
|
| 177 |
+
self.label_mapping = label_mapping
|
| 178 |
+
self.reverse_mapping = {v: k for k, v in label_mapping.items()}
|
| 179 |
+
|
| 180 |
+
def predict(self, X):
|
| 181 |
+
"""Predict with label remapping"""
|
| 182 |
+
pred_mapped = self.model.predict(X)
|
| 183 |
+
return np.array([self.reverse_mapping[pred] for pred in pred_mapped])
|
| 184 |
+
|
| 185 |
+
def predict_proba(self, X):
|
| 186 |
+
"""Predict probabilities"""
|
| 187 |
+
return self.model.predict_proba(X)
|
pyproject.toml
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[project]
|
| 2 |
+
name = "repl-nix-workspace"
|
| 3 |
+
version = "0.1.0"
|
| 4 |
+
description = "Add your description here"
|
| 5 |
+
requires-python = ">=3.11"
|
| 6 |
+
dependencies = [
|
| 7 |
+
"h2>=4.2.0",
|
| 8 |
+
"nsepython>=2.97",
|
| 9 |
+
"numpy>=2.3.0",
|
| 10 |
+
"pandas>=2.3.0",
|
| 11 |
+
"plotly>=6.1.2",
|
| 12 |
+
"scikit-learn>=1.7.0",
|
| 13 |
+
"sqlalchemy>=2.0.41",
|
| 14 |
+
"streamlit>=1.46.0",
|
| 15 |
+
"ta>=0.11.0",
|
| 16 |
+
"xgboost>=3.0.2",
|
| 17 |
+
"yfinance>=0.2.63",
|
| 18 |
+
]
|
requirements.txt
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
streamlit>=1.30.0
|
| 2 |
+
pandas>=2.0.0
|
| 3 |
+
numpy>=1.23.0
|
| 4 |
+
yfinance>=0.2.36
|
| 5 |
+
plotly>=5.18.0
|
| 6 |
+
scikit-learn>=1.2.0
|
| 7 |
+
ta>=0.11.0
|
| 8 |
+
sqlalchemy>=2.0.0
|
| 9 |
+
joblib>=1.2.0
|
| 10 |
+
matplotlib>=3.7.0 # If you're plotting manually outside of plotly
|
run_app.sh
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
export LD_LIBRARY_PATH="/nix/store/055bzdrski1dwqa4km1gxpcjhpn73mng-gcc-10.3.0-lib/lib:/nix/store/026hln0aq1hyshaxsdvhg0kmcm6yf45r-zlib-1.2.13/lib:$LD_LIBRARY_PATH"
|
| 3 |
+
streamlit run app.py --server.port 5005
|
simple_app.py
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import yfinance as yf
|
| 3 |
+
import plotly.graph_objects as go
|
| 4 |
+
from datetime import datetime, timedelta
|
| 5 |
+
import warnings
|
| 6 |
+
warnings.filterwarnings('ignore')
|
| 7 |
+
|
| 8 |
+
# Configure page
|
| 9 |
+
st.set_page_config(
|
| 10 |
+
page_title="AI Stock Trading Signals",
|
| 11 |
+
page_icon="📈",
|
| 12 |
+
layout="wide"
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
def get_stock_data(symbol, period="3mo"):
|
| 16 |
+
"""Get stock data using yfinance"""
|
| 17 |
+
try:
|
| 18 |
+
ticker = yf.Ticker(symbol)
|
| 19 |
+
data = ticker.history(period=period)
|
| 20 |
+
return data
|
| 21 |
+
except Exception as e:
|
| 22 |
+
st.error(f"Error fetching data: {str(e)}")
|
| 23 |
+
return None
|
| 24 |
+
|
| 25 |
+
def get_live_price(stock_symbol):
|
| 26 |
+
"""Get live stock price"""
|
| 27 |
+
try:
|
| 28 |
+
# Try nsepython first
|
| 29 |
+
try:
|
| 30 |
+
import nsepython as nse
|
| 31 |
+
stock_name = stock_symbol.replace('.NS', '')
|
| 32 |
+
live_data = nse.equity_quote(stock_name)
|
| 33 |
+
if live_data and 'lastPrice' in live_data:
|
| 34 |
+
return float(live_data['lastPrice'])
|
| 35 |
+
except:
|
| 36 |
+
pass
|
| 37 |
+
|
| 38 |
+
# Fallback to yfinance
|
| 39 |
+
ticker = yf.Ticker(stock_symbol)
|
| 40 |
+
data = ticker.history(period="1d", interval="1m")
|
| 41 |
+
if not data.empty:
|
| 42 |
+
return float(data['Close'].iloc[-1])
|
| 43 |
+
|
| 44 |
+
return None
|
| 45 |
+
except Exception as e:
|
| 46 |
+
st.warning(f"Could not fetch live price: {str(e)}")
|
| 47 |
+
return None
|
| 48 |
+
|
| 49 |
+
def create_simple_chart(stock_symbol):
|
| 50 |
+
"""Create simple price chart"""
|
| 51 |
+
try:
|
| 52 |
+
data = get_stock_data(stock_symbol, period="1mo")
|
| 53 |
+
if data is None or data.empty:
|
| 54 |
+
return None
|
| 55 |
+
|
| 56 |
+
# Get last 30 days
|
| 57 |
+
data = data.tail(30)
|
| 58 |
+
|
| 59 |
+
fig = go.Figure()
|
| 60 |
+
|
| 61 |
+
# Add candlestick chart
|
| 62 |
+
fig.add_trace(go.Candlestick(
|
| 63 |
+
x=data.index,
|
| 64 |
+
open=data['Open'],
|
| 65 |
+
high=data['High'],
|
| 66 |
+
low=data['Low'],
|
| 67 |
+
close=data['Close'],
|
| 68 |
+
name='Price'
|
| 69 |
+
))
|
| 70 |
+
|
| 71 |
+
# Add simple moving averages
|
| 72 |
+
sma_20 = data['Close'].rolling(window=20).mean()
|
| 73 |
+
sma_50 = data['Close'].rolling(window=50).mean()
|
| 74 |
+
|
| 75 |
+
fig.add_trace(go.Scatter(
|
| 76 |
+
x=data.index,
|
| 77 |
+
y=sma_20,
|
| 78 |
+
name='SMA 20',
|
| 79 |
+
line=dict(color='orange', width=2)
|
| 80 |
+
))
|
| 81 |
+
|
| 82 |
+
fig.add_trace(go.Scatter(
|
| 83 |
+
x=data.index,
|
| 84 |
+
y=sma_50,
|
| 85 |
+
name='SMA 50',
|
| 86 |
+
line=dict(color='blue', width=2)
|
| 87 |
+
))
|
| 88 |
+
|
| 89 |
+
fig.update_layout(
|
| 90 |
+
title=f"{stock_symbol} - Price Chart",
|
| 91 |
+
xaxis_rangeslider_visible=False,
|
| 92 |
+
height=600,
|
| 93 |
+
yaxis_title="Price (₹)"
|
| 94 |
+
)
|
| 95 |
+
|
| 96 |
+
return fig
|
| 97 |
+
|
| 98 |
+
except Exception as e:
|
| 99 |
+
st.error(f"Error creating chart: {str(e)}")
|
| 100 |
+
return None
|
| 101 |
+
|
| 102 |
+
def simple_trading_signal(stock_symbol):
|
| 103 |
+
"""Generate simple trading signal based on moving averages"""
|
| 104 |
+
try:
|
| 105 |
+
data = get_stock_data(stock_symbol, period="3mo")
|
| 106 |
+
if data is None or data.empty:
|
| 107 |
+
return None, "No data available"
|
| 108 |
+
|
| 109 |
+
# Calculate simple moving averages
|
| 110 |
+
current_price = data['Close'].iloc[-1]
|
| 111 |
+
sma_20 = data['Close'].rolling(window=20).mean().iloc[-1]
|
| 112 |
+
sma_50 = data['Close'].rolling(window=50).mean().iloc[-1]
|
| 113 |
+
|
| 114 |
+
# Simple signal logic
|
| 115 |
+
if current_price > sma_20 and sma_20 > sma_50:
|
| 116 |
+
signal = "BUY"
|
| 117 |
+
confidence = 0.75
|
| 118 |
+
elif current_price < sma_20 and sma_20 < sma_50:
|
| 119 |
+
signal = "SELL"
|
| 120 |
+
confidence = 0.75
|
| 121 |
+
else:
|
| 122 |
+
signal = "HOLD"
|
| 123 |
+
confidence = 0.60
|
| 124 |
+
|
| 125 |
+
return {
|
| 126 |
+
'signal': signal,
|
| 127 |
+
'confidence': confidence,
|
| 128 |
+
'current_price': current_price,
|
| 129 |
+
'sma_20': sma_20,
|
| 130 |
+
'sma_50': sma_50
|
| 131 |
+
}, None
|
| 132 |
+
|
| 133 |
+
except Exception as e:
|
| 134 |
+
return None, f"Error generating signal: {str(e)}"
|
| 135 |
+
|
| 136 |
+
def main():
|
| 137 |
+
st.title("📈 Stock Trading Signals App")
|
| 138 |
+
st.markdown("### NSE Stock Analysis (Simplified Version)")
|
| 139 |
+
|
| 140 |
+
# Sidebar for stock selection
|
| 141 |
+
st.sidebar.header("Stock Selection")
|
| 142 |
+
|
| 143 |
+
# Stock options
|
| 144 |
+
stock_options = {
|
| 145 |
+
"Reliance Industries": "RELIANCE.NS",
|
| 146 |
+
"Tata Consultancy Services": "TCS.NS",
|
| 147 |
+
"Infosys": "INFY.NS"
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
selected_stock_name = st.sidebar.selectbox(
|
| 151 |
+
"Choose a stock:",
|
| 152 |
+
list(stock_options.keys())
|
| 153 |
+
)
|
| 154 |
+
|
| 155 |
+
selected_stock = stock_options[selected_stock_name]
|
| 156 |
+
|
| 157 |
+
# Main content
|
| 158 |
+
st.header("📊 Live Market Data")
|
| 159 |
+
|
| 160 |
+
col1, col2 = st.columns(2)
|
| 161 |
+
|
| 162 |
+
with col1:
|
| 163 |
+
if st.button("🔄 Get Live Price", type="primary"):
|
| 164 |
+
live_price = get_live_price(selected_stock)
|
| 165 |
+
if live_price:
|
| 166 |
+
st.metric("Live Price (LTP)", f"₹{live_price:.2f}")
|
| 167 |
+
else:
|
| 168 |
+
st.warning("Could not fetch live price")
|
| 169 |
+
|
| 170 |
+
# Trading signal section
|
| 171 |
+
st.header("🎯 Trading Signal")
|
| 172 |
+
|
| 173 |
+
if st.button("📈 Get Signal", type="primary"):
|
| 174 |
+
signal_result, error = simple_trading_signal(selected_stock)
|
| 175 |
+
|
| 176 |
+
if signal_result:
|
| 177 |
+
signal = signal_result['signal']
|
| 178 |
+
confidence = signal_result['confidence']
|
| 179 |
+
|
| 180 |
+
# Display signal with color coding
|
| 181 |
+
if signal == "BUY":
|
| 182 |
+
st.success(f"🟢 **{signal}** Signal")
|
| 183 |
+
elif signal == "SELL":
|
| 184 |
+
st.error(f"🔴 **{signal}** Signal")
|
| 185 |
+
else:
|
| 186 |
+
st.info(f"🟡 **{signal}** Signal")
|
| 187 |
+
|
| 188 |
+
# Display metrics
|
| 189 |
+
col1, col2, col3 = st.columns(3)
|
| 190 |
+
with col1:
|
| 191 |
+
st.metric("Confidence", f"{confidence:.1%}")
|
| 192 |
+
with col2:
|
| 193 |
+
st.metric("Current Price", f"₹{signal_result['current_price']:.2f}")
|
| 194 |
+
with col3:
|
| 195 |
+
st.metric("SMA 20", f"₹{signal_result['sma_20']:.2f}")
|
| 196 |
+
|
| 197 |
+
else:
|
| 198 |
+
st.error(f"Signal generation failed: {error}")
|
| 199 |
+
|
| 200 |
+
# Chart section
|
| 201 |
+
st.header("📈 Price Chart")
|
| 202 |
+
|
| 203 |
+
if st.button("📊 Show Chart"):
|
| 204 |
+
chart = create_simple_chart(selected_stock)
|
| 205 |
+
if chart:
|
| 206 |
+
st.plotly_chart(chart, use_container_width=True)
|
| 207 |
+
|
| 208 |
+
# Information section
|
| 209 |
+
st.header("ℹ️ About This App")
|
| 210 |
+
|
| 211 |
+
col1, col2 = st.columns(2)
|
| 212 |
+
with col1:
|
| 213 |
+
st.info("**Signal Logic:**\n- BUY: Price > SMA20 > SMA50\n- SELL: Price < SMA20 < SMA50\n- HOLD: Mixed signals")
|
| 214 |
+
|
| 215 |
+
with col2:
|
| 216 |
+
st.info("**Data Source:**\n- Yahoo Finance (yfinance)\n- NSE Python for live prices\n- 3 months historical data")
|
| 217 |
+
|
| 218 |
+
if __name__ == "__main__":
|
| 219 |
+
main()
|
stock_trading.db
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c8187e8a6b515678267a307482a177aac4ce710bcf83f5eb26d5afd5f4a1743b
|
| 3 |
+
size 794624
|
technical_indicators.py
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
import numpy as np
|
| 3 |
+
import ta
|
| 4 |
+
from ta.utils import dropna
|
| 5 |
+
import warnings
|
| 6 |
+
warnings.filterwarnings('ignore')
|
| 7 |
+
|
| 8 |
+
class TechnicalIndicators:
|
| 9 |
+
def __init__(self):
|
| 10 |
+
pass
|
| 11 |
+
|
| 12 |
+
def calculate_all_indicators(self, data):
|
| 13 |
+
"""Calculate comprehensive set of technical indicators"""
|
| 14 |
+
try:
|
| 15 |
+
df = data.copy()
|
| 16 |
+
|
| 17 |
+
# Ensure we have required columns
|
| 18 |
+
if not all(col in df.columns for col in ['Open', 'High', 'Low', 'Close', 'Volume']):
|
| 19 |
+
print("Missing required OHLCV columns")
|
| 20 |
+
return df
|
| 21 |
+
|
| 22 |
+
# Clean data and ensure sufficient data points
|
| 23 |
+
df = df.dropna()
|
| 24 |
+
|
| 25 |
+
if len(df) < 30: # Reduced minimum for testing
|
| 26 |
+
print(f"Insufficient data: {len(df)} rows, need at least 30")
|
| 27 |
+
return df
|
| 28 |
+
|
| 29 |
+
print(f"Calculating indicators for {len(df)} data points...")
|
| 30 |
+
|
| 31 |
+
# Basic Moving Averages
|
| 32 |
+
df = self.add_moving_averages(df)
|
| 33 |
+
|
| 34 |
+
# RSI
|
| 35 |
+
df = self.add_rsi(df)
|
| 36 |
+
|
| 37 |
+
# MACD
|
| 38 |
+
df = self.add_macd(df)
|
| 39 |
+
|
| 40 |
+
# Bollinger Bands
|
| 41 |
+
df = self.add_bollinger_bands(df)
|
| 42 |
+
|
| 43 |
+
# Stochastic Oscillator
|
| 44 |
+
df = self.add_stochastic(df)
|
| 45 |
+
|
| 46 |
+
# Williams %R
|
| 47 |
+
df = self.add_williams_r(df)
|
| 48 |
+
|
| 49 |
+
# Average True Range (ATR)
|
| 50 |
+
df = self.add_atr(df)
|
| 51 |
+
|
| 52 |
+
# Additional indicators
|
| 53 |
+
df = self.add_additional_indicators(df)
|
| 54 |
+
|
| 55 |
+
# Volume indicators
|
| 56 |
+
df = self.add_volume_indicators(df)
|
| 57 |
+
|
| 58 |
+
# Count new indicators added
|
| 59 |
+
original_cols = ['Open', 'High', 'Low', 'Close', 'Volume', 'Dividends', 'Stock Splits']
|
| 60 |
+
new_indicators = [col for col in df.columns if col not in original_cols]
|
| 61 |
+
print(f"Successfully calculated {len(new_indicators)} technical indicators")
|
| 62 |
+
|
| 63 |
+
return df
|
| 64 |
+
|
| 65 |
+
except Exception as e:
|
| 66 |
+
print(f"Error calculating technical indicators: {str(e)}")
|
| 67 |
+
return data
|
| 68 |
+
|
| 69 |
+
def add_moving_averages(self, df):
|
| 70 |
+
"""Add various moving averages"""
|
| 71 |
+
try:
|
| 72 |
+
# Simple Moving Averages using pandas rolling
|
| 73 |
+
df['SMA_10'] = df['Close'].rolling(window=10).mean()
|
| 74 |
+
df['SMA_20'] = df['Close'].rolling(window=20).mean()
|
| 75 |
+
df['SMA_50'] = df['Close'].rolling(window=50).mean()
|
| 76 |
+
|
| 77 |
+
# Exponential Moving Averages using pandas ewm
|
| 78 |
+
df['EMA_12'] = df['Close'].ewm(span=12).mean()
|
| 79 |
+
df['EMA_20'] = df['Close'].ewm(span=20).mean()
|
| 80 |
+
df['EMA_26'] = df['Close'].ewm(span=26).mean()
|
| 81 |
+
|
| 82 |
+
return df
|
| 83 |
+
except Exception as e:
|
| 84 |
+
print(f"Error adding moving averages: {str(e)}")
|
| 85 |
+
return df
|
| 86 |
+
|
| 87 |
+
def add_rsi(self, df, window=14):
|
| 88 |
+
"""Add Relative Strength Index"""
|
| 89 |
+
try:
|
| 90 |
+
# Calculate RSI manually
|
| 91 |
+
delta = df['Close'].diff()
|
| 92 |
+
gain = (delta.where(delta > 0, 0)).rolling(window=window).mean()
|
| 93 |
+
loss = (-delta.where(delta < 0, 0)).rolling(window=window).mean()
|
| 94 |
+
rs = gain / loss
|
| 95 |
+
df['RSI'] = 100 - (100 / (1 + rs))
|
| 96 |
+
return df
|
| 97 |
+
except Exception as e:
|
| 98 |
+
print(f"Error adding RSI: {str(e)}")
|
| 99 |
+
return df
|
| 100 |
+
|
| 101 |
+
def add_macd(self, df):
|
| 102 |
+
"""Add MACD indicators"""
|
| 103 |
+
try:
|
| 104 |
+
# Calculate MACD manually
|
| 105 |
+
ema_12 = df['Close'].ewm(span=12).mean()
|
| 106 |
+
ema_26 = df['Close'].ewm(span=26).mean()
|
| 107 |
+
df['MACD'] = ema_12 - ema_26
|
| 108 |
+
df['MACD_signal'] = df['MACD'].ewm(span=9).mean()
|
| 109 |
+
df['MACD_histogram'] = df['MACD'] - df['MACD_signal']
|
| 110 |
+
return df
|
| 111 |
+
except Exception as e:
|
| 112 |
+
print(f"Error adding MACD: {str(e)}")
|
| 113 |
+
return df
|
| 114 |
+
|
| 115 |
+
def add_bollinger_bands(self, df, window=20, std=2):
|
| 116 |
+
"""Add Bollinger Bands"""
|
| 117 |
+
try:
|
| 118 |
+
# Calculate Bollinger Bands manually
|
| 119 |
+
df['BB_middle'] = df['Close'].rolling(window=window).mean()
|
| 120 |
+
bb_std = df['Close'].rolling(window=window).std()
|
| 121 |
+
df['BB_upper'] = df['BB_middle'] + (bb_std * std)
|
| 122 |
+
df['BB_lower'] = df['BB_middle'] - (bb_std * std)
|
| 123 |
+
df['BB_width'] = (df['BB_upper'] - df['BB_lower']) / df['BB_middle']
|
| 124 |
+
df['BB_position'] = (df['Close'] - df['BB_lower']) / (df['BB_upper'] - df['BB_lower'])
|
| 125 |
+
return df
|
| 126 |
+
except Exception as e:
|
| 127 |
+
print(f"Error adding Bollinger Bands: {str(e)}")
|
| 128 |
+
return df
|
| 129 |
+
|
| 130 |
+
def add_stochastic(self, df, k_window=14, d_window=3):
|
| 131 |
+
"""Add Stochastic Oscillator"""
|
| 132 |
+
try:
|
| 133 |
+
stoch = ta.momentum.StochasticOscillator(df['High'], df['Low'], df['Close'],
|
| 134 |
+
window=k_window, smooth_window=d_window)
|
| 135 |
+
df['Stoch_K'] = stoch.stoch()
|
| 136 |
+
df['Stoch_D'] = stoch.stoch_signal()
|
| 137 |
+
return df
|
| 138 |
+
except Exception as e:
|
| 139 |
+
print(f"Error adding Stochastic: {str(e)}")
|
| 140 |
+
return df
|
| 141 |
+
|
| 142 |
+
def add_williams_r(self, df, window=14):
|
| 143 |
+
"""Add Williams %R"""
|
| 144 |
+
try:
|
| 145 |
+
df['Williams_R'] = ta.momentum.williams_r(df['High'], df['Low'], df['Close'], window=window)
|
| 146 |
+
return df
|
| 147 |
+
except Exception as e:
|
| 148 |
+
print(f"Error adding Williams %R: {str(e)}")
|
| 149 |
+
return df
|
| 150 |
+
|
| 151 |
+
def add_atr(self, df, window=14):
|
| 152 |
+
"""Add Average True Range"""
|
| 153 |
+
try:
|
| 154 |
+
df['ATR'] = ta.volatility.average_true_range(df['High'], df['Low'], df['Close'], window=window)
|
| 155 |
+
df['ATR_ratio'] = df['ATR'] / df['Close'] # Normalized ATR
|
| 156 |
+
return df
|
| 157 |
+
except Exception as e:
|
| 158 |
+
print(f"Error adding ATR: {str(e)}")
|
| 159 |
+
return df
|
| 160 |
+
|
| 161 |
+
def add_additional_indicators(self, df):
|
| 162 |
+
"""Add additional technical indicators"""
|
| 163 |
+
try:
|
| 164 |
+
# Commodity Channel Index
|
| 165 |
+
df['CCI'] = ta.trend.cci(df['High'], df['Low'], df['Close'], window=20)
|
| 166 |
+
|
| 167 |
+
# Rate of Change
|
| 168 |
+
df['ROC'] = ta.momentum.roc(df['Close'], window=12)
|
| 169 |
+
|
| 170 |
+
# Money Flow Index
|
| 171 |
+
df['MFI'] = ta.volume.money_flow_index(df['High'], df['Low'], df['Close'], df['Volume'], window=14)
|
| 172 |
+
|
| 173 |
+
# Parabolic SAR
|
| 174 |
+
df['PSAR'] = ta.trend.psar_down(df['High'], df['Low'], df['Close'])
|
| 175 |
+
|
| 176 |
+
# Aroon indicators
|
| 177 |
+
aroon = ta.trend.AroonIndicator(df['High'], df['Low'], window=25)
|
| 178 |
+
df['Aroon_Up'] = aroon.aroon_up()
|
| 179 |
+
df['Aroon_Down'] = aroon.aroon_down()
|
| 180 |
+
|
| 181 |
+
return df
|
| 182 |
+
except Exception as e:
|
| 183 |
+
print(f"Error adding additional indicators: {str(e)}")
|
| 184 |
+
return df
|
| 185 |
+
|
| 186 |
+
def add_volume_indicators(self, df):
|
| 187 |
+
"""Add volume-based indicators"""
|
| 188 |
+
try:
|
| 189 |
+
# Volume Moving Average
|
| 190 |
+
df['Volume_SMA'] = ta.volume.volume_sma(df['Close'], df['Volume'], window=20)
|
| 191 |
+
|
| 192 |
+
# Volume Weighted Average Price (VWAP)
|
| 193 |
+
df['VWAP'] = ta.volume.volume_weighted_average_price(df['High'], df['Low'], df['Close'], df['Volume'])
|
| 194 |
+
|
| 195 |
+
# On Balance Volume
|
| 196 |
+
df['OBV'] = ta.volume.on_balance_volume(df['Close'], df['Volume'])
|
| 197 |
+
|
| 198 |
+
# Accumulation/Distribution Line
|
| 199 |
+
df['ADL'] = ta.volume.acc_dist_index(df['High'], df['Low'], df['Close'], df['Volume'])
|
| 200 |
+
|
| 201 |
+
# Chaikin Money Flow
|
| 202 |
+
df['CMF'] = ta.volume.chaikin_money_flow(df['High'], df['Low'], df['Close'], df['Volume'], window=20)
|
| 203 |
+
|
| 204 |
+
return df
|
| 205 |
+
except Exception as e:
|
| 206 |
+
print(f"Error adding volume indicators: {str(e)}")
|
| 207 |
+
return df
|
| 208 |
+
|
| 209 |
+
def get_indicator_summary(self, df):
|
| 210 |
+
"""Get summary of all calculated indicators"""
|
| 211 |
+
try:
|
| 212 |
+
indicator_columns = [col for col in df.columns if col not in ['Open', 'High', 'Low', 'Close', 'Volume']]
|
| 213 |
+
|
| 214 |
+
summary = {
|
| 215 |
+
'total_indicators': len(indicator_columns),
|
| 216 |
+
'trend_indicators': [col for col in indicator_columns if any(x in col for x in ['SMA', 'EMA', 'MACD', 'PSAR', 'Aroon'])],
|
| 217 |
+
'momentum_indicators': [col for col in indicator_columns if any(x in col for x in ['RSI', 'Stoch', 'Williams', 'ROC', 'CCI'])],
|
| 218 |
+
'volatility_indicators': [col for col in indicator_columns if any(x in col for x in ['BB', 'ATR'])],
|
| 219 |
+
'volume_indicators': [col for col in indicator_columns if any(x in col for x in ['Volume', 'VWAP', 'OBV', 'ADL', 'CMF', 'MFI'])]
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
return summary
|
| 223 |
+
except Exception as e:
|
| 224 |
+
print(f"Error getting indicator summary: {str(e)}")
|
| 225 |
+
return {}
|
uv.lock
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|