Spaces:
Running
Running
Upload folder using huggingface_hub
Browse files- .gitignore +16 -0
- .gitmodules +3 -0
- .vscode/settings.json +5 -0
- LICENSE +202 -0
- OAI_CONFIG_LIST_sample +28 -0
- README.md +309 -8
- agent_builder_demo.py +44 -0
- app.py +199 -0
- config_api_keys_sample +6 -0
- configs/save_config_forecaster.json +43 -0
- experiments/investment_group.json +188 -0
- experiments/multi_factor_agents.py +121 -0
- experiments/portfolio_optimization.py +312 -0
- experiments/quantitative_investment_group_config.json +46 -0
- finrobot/__init__.py +0 -0
- finrobot/agents/__init__.py +1 -0
- finrobot/agents/agent_library.py +97 -0
- finrobot/agents/utils.py +12 -0
- finrobot/agents/workflow.py +175 -0
- finrobot/data_source/__init__.py +13 -0
- finrobot/data_source/finnhub_utils.py +161 -0
- finrobot/data_source/finnlp_utils.py +234 -0
- finrobot/data_source/fmp_utils.py +200 -0
- finrobot/data_source/sec_utils.py +89 -0
- finrobot/data_source/yfinance_utils.py +117 -0
- finrobot/functional/__init__.py +6 -0
- finrobot/functional/analyzer.py +344 -0
- finrobot/functional/charting.py +235 -0
- finrobot/functional/coding.py +89 -0
- finrobot/functional/quantitative.py +184 -0
- finrobot/functional/reportlab.py +391 -0
- finrobot/functional/text.py +19 -0
- finrobot/toolkits.py +106 -0
- finrobot/utils.py +83 -0
- report/Microsoft_Annual_Report_2023.pdf +0 -0
- report/NVDA_report.pdf +0 -0
- requirements.txt +27 -0
- servercn.py +198 -0
- setup.py +41 -0
- test_module.py +89 -0
- tutorials_advanced/agent_annual_report.ipynb +0 -0
- tutorials_advanced/agent_fingpt_forecaster.ipynb +503 -0
- tutorials_advanced/agent_trade_strategist.ipynb +0 -0
- tutorials_advanced/lmm_agent_mplfinance.ipynb +0 -0
- tutorials_advanced/lmm_agent_opt_smacross.ipynb +0 -0
- tutorials_advanced/servercn.py +198 -0
- tutorials_advanced/serveren.py +199 -0
- tutorials_beginner/agent_annual_report.ipynb +0 -0
- tutorials_beginner/agent_fingpt_forecaster.ipynb +395 -0
- tutorials_beginner/agent_rag_qa.ipynb +0 -0
.gitignore
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
OAI_CONFIG_LIST
|
2 |
+
config_api_keys
|
3 |
+
__pycache__/
|
4 |
+
.ipynb_checkpoints/
|
5 |
+
.cache/
|
6 |
+
*.csv
|
7 |
+
*.jpg
|
8 |
+
*.png
|
9 |
+
quant/
|
10 |
+
coding/
|
11 |
+
*.egg-info/
|
12 |
+
.DS_Store
|
13 |
+
projects/
|
14 |
+
dist/
|
15 |
+
tmp/
|
16 |
+
*.env
|
.gitmodules
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
[submodule "FinNLP"]
|
2 |
+
path = FinNLP
|
3 |
+
url = https://github.com/AI4Finance-Foundation/FinNLP.git
|
.vscode/settings.json
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"python.analysis.extraPaths": [
|
3 |
+
"./FinNLP"
|
4 |
+
]
|
5 |
+
}
|
LICENSE
ADDED
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 2024 AI4Finance Foundation Inc.
|
190 |
+
All rights reserved.
|
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.
|
202 |
+
|
OAI_CONFIG_LIST_sample
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// Please modify the content, remove these four lines of comment and rename this file to OAI_CONFIG_LIST to run the sample code.
|
2 |
+
// If using pyautogen v0.1.x with Azure OpenAI, please replace "base_url" with "api_base" (line 13 and line 20 below). Use "pip list" to check version of pyautogen installed.
|
3 |
+
//
|
4 |
+
// NOTE: This configuration lists GPT-4 as the default model, as this represents our current recommendation, and is known to work well with AutoGen. If you use a model other than GPT-4, you may need to revise various system prompts (especially if using weaker models like GPT-3.5-turbo). Moreover, if you use models other than those hosted by OpenAI or Azure, you may incur additional risks related to alignment and safety. Proceed with caution if updating this default.
|
5 |
+
[
|
6 |
+
{
|
7 |
+
"model": "gpt-4-0125-preview",
|
8 |
+
"api_key": "<your OpenAI API key here>"
|
9 |
+
},
|
10 |
+
{
|
11 |
+
"model": "gpt-4-1106-vision-preview",
|
12 |
+
"api_key": "<your OpenAI API key here>"
|
13 |
+
},
|
14 |
+
{
|
15 |
+
"model": "<your Azure OpenAI deployment name>",
|
16 |
+
"api_key": "<your Azure OpenAI API key here>",
|
17 |
+
"base_url": "<your Azure OpenAI API base here>",
|
18 |
+
"api_type": "azure",
|
19 |
+
"api_version": "2024-02-15-preview"
|
20 |
+
},
|
21 |
+
{
|
22 |
+
"model": "<your Azure OpenAI deployment name>",
|
23 |
+
"api_key": "<your Azure OpenAI API key here>",
|
24 |
+
"base_url": "<your Azure OpenAI API base here>",
|
25 |
+
"api_type": "azure",
|
26 |
+
"api_version": "2024-02-15-preview"
|
27 |
+
}
|
28 |
+
]
|
README.md
CHANGED
@@ -1,12 +1,313 @@
|
|
1 |
---
|
2 |
-
title:
|
3 |
-
emoji: 🐢
|
4 |
-
colorFrom: blue
|
5 |
-
colorTo: red
|
6 |
-
sdk: gradio
|
7 |
-
sdk_version: 4.36.1
|
8 |
app_file: app.py
|
9 |
-
|
|
|
10 |
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
|
12 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
1 |
---
|
2 |
+
title: finaien
|
|
|
|
|
|
|
|
|
|
|
3 |
app_file: app.py
|
4 |
+
sdk: gradio
|
5 |
+
sdk_version: 4.31.5
|
6 |
---
|
7 |
+
<div align="center">
|
8 |
+
<img align="center" width="30%" alt="image" src="https://github.com/AI4Finance-Foundation/FinGPT/assets/31713746/e0371951-1ce1-488e-aa25-0992dafcc139">
|
9 |
+
</div>
|
10 |
+
|
11 |
+
# FinRobot: An Open-Source AI Agent Platform for Financial Applications using Large Language Models
|
12 |
+
[]([https://pepy.tech/project/finrobot](https://pepy.tech/project/finrobot))
|
13 |
+
[](https://pepy.tech/project/finrobot)
|
14 |
+
[](https://www.python.org/downloads/release/python-360/)
|
15 |
+
[](https://pypi.org/project/finrobot/)
|
16 |
+

|
17 |
+
|
18 |
+
<div align="center">
|
19 |
+
<img align="center" src=figs/logo_white_background.jpg width="40%"/>
|
20 |
+
</div>
|
21 |
+
|
22 |
+
**FinRobot** is an AI Agent Platform that transcends the scope of FinGPT, representing a comprehensive solution meticulously designed for financial applications. It integrates **a diverse array of AI technologies**, extending beyond mere language models. This expansive vision highlights the platform's versatility and adaptability, addressing the multifaceted needs of the financial industry.
|
23 |
+
|
24 |
+
**Concept of AI Agent**: an AI Agent is an intelligent entity that uses large language models as its brain to perceive its environment, make decisions, and execute actions. Unlike traditional artificial intelligence, AI Agents possess the ability to independently think and utilize tools to progressively achieve given objectives.
|
25 |
+
|
26 |
+
[Whitepaper of FinRobot](https://arxiv.org/abs/2405.14767)
|
27 |
+
|
28 |
+
[](https://discord.gg/trsr8SXpW5)
|
29 |
+
|
30 |
+
## FinRobot Ecosystem
|
31 |
+
<div align="center">
|
32 |
+
<img align="center" src="https://github.com/AI4Finance-Foundation/FinRobot/assets/31713746/6b30d9c1-35e5-4d36-a138-7e2769718f62" width="90%"/>
|
33 |
+
</div>
|
34 |
+
|
35 |
+
### The overall framework of FinRobot is organized into four distinct layers, each designed to address specific aspects of financial AI processing and application:
|
36 |
+
1. **Financial AI Agents Layer**: The Financial AI Agents Layer now includes Financial Chain-of-Thought (CoT) prompting, enhancing complex analysis and decision-making capacity. Market Forecasting Agents, Document Analysis Agents, and Trading Strategies Agents utilize CoT to dissect financial challenges into logical steps, aligning their advanced algorithms and domain expertise with the evolving dynamics of financial markets for precise, actionable insights.
|
37 |
+
2. **Financial LLMs Algorithms Layer**: The Financial LLMs Algorithms Layer configures and utilizes specially tuned models tailored to specific domains and global market analysis.
|
38 |
+
3. **LLMOps and DataOps Layers**: The LLMOps layer implements a multi-source integration strategy that selects the most suitable LLMs for specific financial tasks, utilizing a range of state-of-the-art models.
|
39 |
+
4. **Multi-source LLM Foundation Models Layer**: This foundational layer supports the plug-and-play functionality of various general and specialized LLMs.
|
40 |
+
|
41 |
+
|
42 |
+
## FinRobot: Agent Workflow
|
43 |
+
<div align="center">
|
44 |
+
<img align="center" src="https://github.com/AI4Finance-Foundation/FinRobot/assets/31713746/ff8033be-2326-424a-ac11-17e2c9c4983d" width="60%"/>
|
45 |
+
</div>
|
46 |
+
|
47 |
+
1. **Perception**: This module captures and interprets multimodal financial data from market feeds, news, and economic indicators, using sophisticated techniques to structure the data for thorough analysis.
|
48 |
+
|
49 |
+
2. **Brain**: Acting as the core processing unit, this module perceives data from the Perception module with LLMs and utilizes Financial Chain-of-Thought (CoT) processes to generate structured instructions.
|
50 |
+
|
51 |
+
3. **Action**: This module executes instructions from the Brain module, applying tools to translate analytical insights into actionable outcomes. Actions include trading, portfolio adjustments, generating reports, or sending alerts, thereby actively influencing the financial environment.
|
52 |
+
|
53 |
+
## FinRobot: Smart Scheduler
|
54 |
+
<div align="center">
|
55 |
+
<img align="center" src="https://github.com/AI4Finance-Foundation/FinRobot/assets/31713746/06fa0b78-ac53-48d3-8a6e-98d15386327e" width="60%"/>
|
56 |
+
</div>
|
57 |
+
|
58 |
+
The Smart Scheduler is central to ensuring model diversity and optimizing the integration and selection of the most appropriate LLM for each task.
|
59 |
+
* **Director Agent**: This component orchestrates the task assignment process, ensuring that tasks are allocated to agents based on their performance metrics and suitability for specific tasks.
|
60 |
+
* **Agent Registration**: Manages the registration and tracks the availability of agents within the system, facilitating an efficient task allocation process.
|
61 |
+
* **Agent Adaptor**: Tailor agent functionalities to specific tasks, enhancing their performance and integration within the overall system.
|
62 |
+
* **Task Manager**: Manages and stores different general and fine-tuned LLMs-based agents tailored for various financial tasks, updated periodically to ensure relevance and efficacy.
|
63 |
+
|
64 |
+
## File Structure
|
65 |
+
|
66 |
+
The main folder **finrobot** has three subfolders **agents, data_source, functional**.
|
67 |
+
|
68 |
+
```
|
69 |
+
FinRobot
|
70 |
+
├── finrobot (main folder)
|
71 |
+
│ ├── agents
|
72 |
+
│ ├── agent_library.py
|
73 |
+
│ └── workflow.py
|
74 |
+
│ ├── data_source
|
75 |
+
│ ├── finnhub_utils.py
|
76 |
+
│ ├── finnlp_utils.py
|
77 |
+
│ ├── fmp_utils.py
|
78 |
+
│ ├── sec_utils.py
|
79 |
+
│ └── yfinance_utils.py
|
80 |
+
│ ├── functional
|
81 |
+
│ ├── analyzer.py
|
82 |
+
│ ├── charting.py
|
83 |
+
│ ├── coding.py
|
84 |
+
│ ├── quantitative.py
|
85 |
+
│ ├── reportlab.py
|
86 |
+
│ └── text.py
|
87 |
+
│ ├── toolkits.py
|
88 |
+
│ └── utils.py
|
89 |
+
│
|
90 |
+
├── configs
|
91 |
+
├── experiments
|
92 |
+
├── tutorials_beginner (hands-on tutorial)
|
93 |
+
│ ├── agent_fingpt_forecaster.ipynb
|
94 |
+
│ └── agent_annual_report.ipynb
|
95 |
+
├── tutorials_advanced (advanced tutorials for potential finrobot developers)
|
96 |
+
│ ├── agent_trade_strategist.ipynb
|
97 |
+
│ ├── agent_fingpt_forecaster.ipynb
|
98 |
+
│ ├── agent_annual_report.ipynb
|
99 |
+
│ ├── lmm_agent_mplfinance.ipynb
|
100 |
+
│ └── lmm_agent_opt_smacross.ipynb
|
101 |
+
├── setup.py
|
102 |
+
├── OAI_CONFIG_LIST_sample
|
103 |
+
├── config_api_keys_sample
|
104 |
+
├── requirements.txt
|
105 |
+
└── README.md
|
106 |
+
```
|
107 |
+
|
108 |
+
## Installation:
|
109 |
+
|
110 |
+
**1. (Recommended) Create a new virtual environment**
|
111 |
+
```shell
|
112 |
+
conda create --name finrobot python=3.10
|
113 |
+
conda activate finrobot
|
114 |
+
```
|
115 |
+
**2. download the FinRobot repo use terminal or download it manually**
|
116 |
+
```shell
|
117 |
+
git clone https://github.com/AI4Finance-Foundation/FinRobot.git
|
118 |
+
cd FinRobot
|
119 |
+
```
|
120 |
+
**3. install finrobot & dependencies from source or pypi**
|
121 |
+
|
122 |
+
get our latest release from pypi
|
123 |
+
```bash
|
124 |
+
pip install -U finrobot
|
125 |
+
```
|
126 |
+
or install from this repo directly
|
127 |
+
```
|
128 |
+
pip install -e .
|
129 |
+
```
|
130 |
+
**4. modify OAI_CONFIG_LIST_sample file**
|
131 |
+
```shell
|
132 |
+
1) rename OAI_CONFIG_LIST_sample to OAI_CONFIG_LIST
|
133 |
+
2) remove the four lines of comment within the OAI_CONFIG_LIST file
|
134 |
+
3) add your own openai api-key <your OpenAI API key here>
|
135 |
+
```
|
136 |
+
**5. modify config_api_keys_sample file**
|
137 |
+
```shell
|
138 |
+
1) rename config_api_keys_sample to config_api_keys
|
139 |
+
2) remove the comment within the config_api_keys file
|
140 |
+
3) add your own finnhub-api "YOUR_FINNHUB_API_KEY"
|
141 |
+
4) add your own financialmodelingprep and sec-api keys "YOUR_FMP_API_KEY" and "YOUR_SEC_API_KEY" (for financial report generation)
|
142 |
+
```
|
143 |
+
**6. start navigating the tutorials or the demos below:**
|
144 |
+
```
|
145 |
+
# find these notebooks in tutorials
|
146 |
+
1) agent_annual_report.ipynb
|
147 |
+
2) agent_fingpt_forecaster.ipynb
|
148 |
+
3) agent_trade_strategist.ipynb
|
149 |
+
4) lmm_agent_mplfinance.ipynb
|
150 |
+
5) lmm_agent_opt_smacross.ipynb
|
151 |
+
```
|
152 |
+
|
153 |
+
## Demos
|
154 |
+
### 1. Market Forecaster Agent (Predict Stock Movements Direction)
|
155 |
+
Takes a company's ticker symbol, recent basic financials, and market news as input and predicts its stock movements.
|
156 |
+
|
157 |
+
1. Import
|
158 |
+
```python
|
159 |
+
import autogen
|
160 |
+
from finrobot.utils import get_current_date, register_keys_from_json
|
161 |
+
from finrobot.agents.workflow import SingleAssistant
|
162 |
+
```
|
163 |
+
2. Config
|
164 |
+
```python
|
165 |
+
# Read OpenAI API keys from a JSON file
|
166 |
+
llm_config = {
|
167 |
+
"config_list": autogen.config_list_from_json(
|
168 |
+
"../OAI_CONFIG_LIST",
|
169 |
+
filter_dict={"model": ["gpt-4-0125-preview"]},
|
170 |
+
),
|
171 |
+
"timeout": 120,
|
172 |
+
"temperature": 0,
|
173 |
+
}
|
174 |
+
|
175 |
+
# Register FINNHUB API keys
|
176 |
+
register_keys_from_json("../config_api_keys")
|
177 |
+
```
|
178 |
+
3. Run
|
179 |
+
```python
|
180 |
+
company = "NVDA"
|
181 |
+
|
182 |
+
assitant = SingleAssistant(
|
183 |
+
"Market_Analyst",
|
184 |
+
llm_config,
|
185 |
+
# set to "ALWAYS" if you want to chat instead of simply receiving the prediciton
|
186 |
+
human_input_mode="NEVER",
|
187 |
+
)
|
188 |
+
assitant.chat(
|
189 |
+
f"Use all the tools provided to retrieve information available for {company} upon {get_current_date()}. Analyze the positive developments and potential concerns of {company} "
|
190 |
+
"with 2-4 most important factors respectively and keep them concise. Most factors should be inferred from company related news. "
|
191 |
+
f"Then make a rough prediction (e.g. up/down by 2-3%) of the {company} stock price movement for next week. Provide a summary analysis to support your prediction."
|
192 |
+
)
|
193 |
+
```
|
194 |
+
4. Result
|
195 |
+
<div align="center">
|
196 |
+
<img align="center" src="https://github.com/AI4Finance-Foundation/FinRobot/assets/31713746/812ec23a-9cb3-4fad-b716-78533ddcd9dc" width="40%"/>
|
197 |
+
<img align="center" src="https://github.com/AI4Finance-Foundation/FinRobot/assets/31713746/9a2f9f48-b0e1-489c-8679-9a4c530f313c" width="41%"/>
|
198 |
+
</div>
|
199 |
+
|
200 |
+
### 2. Financial Analyst Agent for Report Writing (Equity Research Report)
|
201 |
+
Take a company's 10-k form, financial data, and market data as input and output an equity research report
|
202 |
+
|
203 |
+
1. Import
|
204 |
+
```python
|
205 |
+
import os
|
206 |
+
import autogen
|
207 |
+
from textwrap import dedent
|
208 |
+
from finrobot.utils import register_keys_from_json
|
209 |
+
from finrobot.agents.workflow import SingleAssistantShadow
|
210 |
+
```
|
211 |
+
2. Config
|
212 |
+
```python
|
213 |
+
llm_config = {
|
214 |
+
"config_list": autogen.config_list_from_json(
|
215 |
+
"../OAI_CONFIG_LIST",
|
216 |
+
filter_dict={
|
217 |
+
"model": ["gpt-4-0125-preview"],
|
218 |
+
},
|
219 |
+
),
|
220 |
+
"timeout": 120,
|
221 |
+
"temperature": 0.5,
|
222 |
+
}
|
223 |
+
register_keys_from_json("../config_api_keys")
|
224 |
+
|
225 |
+
# Intermediate strategy modules will be saved in this directory
|
226 |
+
work_dir = "../report"
|
227 |
+
os.makedirs(work_dir, exist_ok=True)
|
228 |
+
|
229 |
+
assistant = SingleAssistantShadow(
|
230 |
+
"Expert_Investor",
|
231 |
+
llm_config,
|
232 |
+
max_consecutive_auto_reply=None,
|
233 |
+
human_input_mode="TERMINATE",
|
234 |
+
)
|
235 |
+
|
236 |
+
```
|
237 |
+
3. Run
|
238 |
+
```python
|
239 |
+
company = "Microsoft"
|
240 |
+
fyear = "2023"
|
241 |
+
|
242 |
+
message = dedent(
|
243 |
+
f"""
|
244 |
+
With the tools you've been provided, write an annual report based on {company}'s {fyear} 10-k report, format it into a pdf.
|
245 |
+
Pay attention to the followings:
|
246 |
+
- Explicitly explain your working plan before you kick off.
|
247 |
+
- Use tools one by one for clarity, especially when asking for instructions.
|
248 |
+
- All your file operations should be done in "{work_dir}".
|
249 |
+
- Display any image in the chat once generated.
|
250 |
+
- All the paragraphs should combine between 400 and 450 words, don't generate the pdf until this is explicitly fulfilled.
|
251 |
+
"""
|
252 |
+
)
|
253 |
+
|
254 |
+
assistant.chat(message, use_cache=True, max_turns=50,
|
255 |
+
summary_method="last_msg")
|
256 |
+
```
|
257 |
+
4. Result
|
258 |
+
<div align="center">
|
259 |
+
<img align="center" src="https://github.com/AI4Finance-Foundation/FinRobot/assets/31713746/d2d999e0-dc0e-4196-aca1-218f5fadcc5b" width="60%"/>
|
260 |
+
<img align="center" src="https://github.com/AI4Finance-Foundation/FinRobot/assets/31713746/3a21873f-9498-4d73-896b-3740bf6d116d" width="60%"/>
|
261 |
+
</div>
|
262 |
+
|
263 |
+
**Financial CoT**:
|
264 |
+
1. **Gather Preliminary Data**: 10-K report, market data, financial ratios
|
265 |
+
2. **Analyze Financial Statements**: balance sheet, income statement, cash flow
|
266 |
+
3. **Company Overview and Performance**: company description, business highlights, segment analysis
|
267 |
+
4. **Risk Assessment**: assess risks
|
268 |
+
5. **Financial Performance Visualization**: plot PE ratio and EPS
|
269 |
+
6. **Synthesize Findings into Paragraphs**: combine all parts into a coherent summary
|
270 |
+
7. **Generate PDF Report**: use tools to generate PDF automatically
|
271 |
+
8. **Quality Assurance**: check word counts
|
272 |
+
|
273 |
+
### 3. Trade Strategist Agent with multimodal capabilities
|
274 |
+
|
275 |
+
|
276 |
+
## AI Agent Papers
|
277 |
+
|
278 |
+
+ [Stanford University + Microsoft Research] [Agent AI: Surveying the Horizons of Multimodal Interaction](https://arxiv.org/abs/2401.03568)
|
279 |
+
+ [Stanford University] [Generative Agents: Interactive Simulacra of Human Behavior](https://arxiv.org/abs/2304.03442)
|
280 |
+
+ [Fudan NLP Group] [The Rise and Potential of Large Language Model Based Agents: A Survey](https://arxiv.org/abs/2309.07864)
|
281 |
+
+ [Fudan NLP Group] [LLM-Agent-Paper-List](https://github.com/WooooDyy/LLM-Agent-Paper-List)
|
282 |
+
+ [Tsinghua University] [Large Language Models Empowered Agent-based Modeling and Simulation: A Survey and Perspectives](https://arxiv.org/abs/2312.11970)
|
283 |
+
+ [Renmin University] [A Survey on Large Language Model-based Autonomous Agents](https://arxiv.org/pdf/2308.11432.pdf)
|
284 |
+
+ [Nanyang Technological University] [FinAgent: A Multimodal Foundation Agent for Financial Trading: Tool-Augmented, Diversified, and Generalist](https://arxiv.org/abs/2402.18485)
|
285 |
+
|
286 |
+
## AI Agent Blogs and Videos
|
287 |
+
+ [Medium] [An Introduction to AI Agents](https://medium.com/humansdotai/an-introduction-to-ai-agents-e8c4afd2ee8f)
|
288 |
+
+ [Medium] [Unmasking the Best Character AI Chatbots | 2024](https://medium.com/@aitrendorbit/unmasking-the-best-character-ai-chatbots-2024-351de43792f4#the-best-character-ai-chatbots)
|
289 |
+
+ [big-picture] [ChatGPT, Next Level: Meet 10 Autonomous AI Agents](https://blog.big-picture.com/en/chatgpt-next-level-meet-10-autonomous-ai-agents-auto-gpt-babyagi-agentgpt-microsoft-jarvis-chaosgpt-friends/)
|
290 |
+
+ [TowardsDataScience] [Navigating the World of LLM Agents: A Beginner’s Guide](https://towardsdatascience.com/navigating-the-world-of-llm-agents-a-beginners-guide-3b8d499db7a9)
|
291 |
+
+ [YouTube] [Introducing Devin - The "First" AI Agent Software Engineer](https://www.youtube.com/watch?v=iVbN95ica_k)
|
292 |
+
|
293 |
+
|
294 |
+
## AI Agent Open-Source Framework & Tool
|
295 |
+
+ [AutoGPT (161k stars)](https://github.com/Significant-Gravitas/AutoGPT) is a tool for everyone to use, aiming to democratize AI, making it accessible for everyone to use and build upon.
|
296 |
+
+ [LangChain (82.7k stars)](https://github.com/langchain-ai/langchain) is a framework for developing context-aware applications powered by language models, enabling them to connect to sources of context and rely on the model's reasoning capabilities for responses and actions.
|
297 |
+
+ [MetaGPT (39.1k stars)](https://github.com/geekan/MetaGPT) is a multi-agent open-source framework that assigns different roles to GPTs, forming a collaborative software entity to execute complex tasks.
|
298 |
+
+ [AutoGen (24.8k stars)](https://github.com/microsoft/autogen) is a framework for developing LLM applications with conversational agents that collaborate to solve tasks. These agents are customizable, support human interaction, and operate in modes combining LLMs, human inputs, and tools.
|
299 |
+
+ [dify (22.7k stars)](https://github.com/langgenius/dify) is an LLM application development platform. It integrates the concepts of Backend as a Service and LLMOps, covering the core tech stack required for building generative AI-native applications, including a built-in RAG engine
|
300 |
+
+ [ChatDev (22.7k stars)](https://github.com/OpenBMB/ChatDev) is a framework that focuses on developing conversational AI Agents capable of dialogue and question-answering. It provides a range of pre-trained models and interactive interfaces, facilitating the development of customized chat Agents for users.
|
301 |
+
+ [BabyAGI (19.2k stars)](https://github.com/yoheinakajima/babyagi) is an AI-powered task management system, dedicated to building AI Agents with preliminary general intelligence.
|
302 |
+
+ [SuperAGI (14.4k stars)](https://github.com/TransformerOptimus/SuperAGI) is a dev-first open-source autonomous AI agent framework enabling developers to build, manage & run useful autonomous agents.
|
303 |
+
+ [FastGPT (12.5k stars)](https://github.com/labring/FastGPT) is a knowledge-based platform built on the LLM, offers out-of-the-box data processing and model invocation capabilities, allows for workflow orchestration through Flow visualization.
|
304 |
+
+ [CrewAI (12.1k stars)](https://github.com/joaomdmoura/crewAI) is a framework for orchestrating role-playing, autonomous AI agents. By fostering collaborative intelligence, CrewAI empowers agents to work together seamlessly, tackling complex tasks.
|
305 |
+
+ [XAgent (7.5k stars)](https://github.com/OpenBMB/XAgent) is an open-source experimental Large Language Model (LLM) driven autonomous agent that can automatically solve various tasks.
|
306 |
+
+ [Bisheng (5.5k stars)](https://github.com/dataelement/bisheng) is a leading open-source platform for developing LLM applications.
|
307 |
+
+ [Voyager (5.1k stars)](https://github.com/OpenBMB/XAgent) An Open-Ended Embodied Agent with Large Language Models.
|
308 |
+
+ [CAMEL (4.4k stars)](https://github.com/camel-ai/camel) is a framework that offers a comprehensive set of tools and algorithms for building multimodal AI Agents, enabling them to handle various data forms such as text, images, and speech.
|
309 |
+
+ [Langfuse (2.9k stars)](https://github.com/langfuse/langfuse) is a language fusion framework that can integrate the language abilities of multiple AI Agents, enabling them to simultaneously possess multilingual understanding and generation capabilities.
|
310 |
+
|
311 |
+
**Disclaimer**: The codes and documents provided herein are released under the Apache-2.0 license. They should not be construed as financial counsel or recommendations for live trading. It is imperative to exercise caution and consult with qualified financial professionals prior to any trading or investment actions.
|
312 |
+
|
313 |
|
|
agent_builder_demo.py
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import autogen
|
3 |
+
from autogen.agentchat.contrib.agent_builder import AgentBuilder
|
4 |
+
from finrobot.utils import get_current_date
|
5 |
+
|
6 |
+
|
7 |
+
config_file_or_env = "OAI_CONFIG_LIST"
|
8 |
+
llm_config = {"temperature": 0}
|
9 |
+
|
10 |
+
builder = AgentBuilder(
|
11 |
+
config_file_or_env=config_file_or_env,
|
12 |
+
builder_model="gpt-4-0125-preview",
|
13 |
+
agent_model="gpt-4-0125-preview",
|
14 |
+
)
|
15 |
+
|
16 |
+
config_list = autogen.config_list_from_json(
|
17 |
+
config_file_or_env, filter_dict={"model": ["gpt-4-0125-preview"]}
|
18 |
+
)
|
19 |
+
|
20 |
+
building_task = "Gather information like company profile, recent stock price fluctuations, market news, and financial basics of a specified company (e.g. AAPL) by programming and analyze its current positive developments and potential concerns. Based on all the information, come up with a rough estimate (e.g. up by 2-3%) and give a summary of the reasons for next week's stock price. Each python program should execute on its own, and avoid plotting any chart."
|
21 |
+
config_path = "configs/save_config_forecaster.json"
|
22 |
+
|
23 |
+
if os.path.exists(config_path):
|
24 |
+
agent_list, agent_config = builder.load(config_path)
|
25 |
+
else:
|
26 |
+
agent_list, agent_configs = builder.build(
|
27 |
+
building_task,
|
28 |
+
llm_config,
|
29 |
+
coding=True,
|
30 |
+
code_execution_config={
|
31 |
+
"work_dir": "coding",
|
32 |
+
"use_docker": False,
|
33 |
+
},
|
34 |
+
)
|
35 |
+
builder.save(config_path)
|
36 |
+
|
37 |
+
group_chat = autogen.GroupChat(agents=agent_list, messages=[], max_round=20)
|
38 |
+
manager = autogen.GroupChatManager(
|
39 |
+
groupchat=group_chat, llm_config={"config_list": config_list, **llm_config}
|
40 |
+
)
|
41 |
+
agent_list[0].initiate_chat(
|
42 |
+
manager,
|
43 |
+
message=f"Today is {get_current_date()}, predict next week's stock price for Nvidia with its recent market news and stock price movements.",
|
44 |
+
)
|
app.py
ADDED
@@ -0,0 +1,199 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import json
|
3 |
+
import pandas as pd
|
4 |
+
from datetime import date
|
5 |
+
import gradio as gr
|
6 |
+
import autogen
|
7 |
+
from autogen.cache import Cache
|
8 |
+
from finrobot.utils import get_current_date
|
9 |
+
from finrobot.data_source import FinnHubUtils, YFinanceUtils
|
10 |
+
from dotenv import load_dotenv
|
11 |
+
|
12 |
+
# Load environment variables from .env file
|
13 |
+
load_dotenv()
|
14 |
+
|
15 |
+
# Utility functions
|
16 |
+
def save_output(data: pd.DataFrame, tag: str, save_path: str = None) -> None:
|
17 |
+
if save_path:
|
18 |
+
data.to_csv(save_path)
|
19 |
+
print(f"{tag} saved to {save_path}")
|
20 |
+
|
21 |
+
def get_current_date():
|
22 |
+
return date.today().strftime("%Y-%m-%d")
|
23 |
+
|
24 |
+
def register_keys():
|
25 |
+
keys = {
|
26 |
+
"FINNHUB_API_KEY": os.getenv("FINNHUB_API_KEY"),
|
27 |
+
"FMP_API_KEY": os.getenv("FMP_API_KEY"),
|
28 |
+
"SEC_API_KEY": os.getenv("SEC_API_KEY")
|
29 |
+
}
|
30 |
+
for key, value in keys.items():
|
31 |
+
if value:
|
32 |
+
os.environ[key] = value
|
33 |
+
|
34 |
+
def read_response_from_md(filename):
|
35 |
+
with open(filename, "r") as file:
|
36 |
+
content = file.read()
|
37 |
+
return content
|
38 |
+
|
39 |
+
def save_to_md(content, filename):
|
40 |
+
with open(filename, "w") as file: # Use write mode to overwrite the file
|
41 |
+
file.write(content + "\n")
|
42 |
+
print(f"Content saved to {filename}")
|
43 |
+
|
44 |
+
# Initialize LLM configuration
|
45 |
+
config_list = [
|
46 |
+
{
|
47 |
+
"model": "gpt-4o",
|
48 |
+
"api_key": os.getenv("OPENAI_API_KEY")
|
49 |
+
}
|
50 |
+
]
|
51 |
+
llm_config = {"config_list": config_list, "timeout": 120, "temperature": 0}
|
52 |
+
|
53 |
+
# Register FINNHUB API keys
|
54 |
+
register_keys()
|
55 |
+
|
56 |
+
# Define agents
|
57 |
+
analyst = autogen.AssistantAgent(
|
58 |
+
name="Market_Analyst",
|
59 |
+
system_message="As a Market Analyst, one must possess strong analytical and problem-solving abilities, collect necessary financial information and aggregate them based on client's requirement. For coding tasks, only use the functions you have been provided with. Reply TERMINATE when the task is done.",
|
60 |
+
llm_config=llm_config,
|
61 |
+
)
|
62 |
+
|
63 |
+
user_proxy = autogen.UserProxyAgent(
|
64 |
+
name="User_Proxy",
|
65 |
+
is_termination_msg=lambda x: x.get("content", "") and x.get("content", "").strip().endswith("TERMINATE"),
|
66 |
+
human_input_mode="NEVER",
|
67 |
+
max_consecutive_auto_reply=10,
|
68 |
+
code_execution_config={
|
69 |
+
"work_dir": "coding",
|
70 |
+
"use_docker": False,
|
71 |
+
},
|
72 |
+
)
|
73 |
+
|
74 |
+
# Register tools
|
75 |
+
from finrobot.toolkits import register_toolkits
|
76 |
+
|
77 |
+
tools = [
|
78 |
+
{
|
79 |
+
"function": FinnHubUtils.get_company_profile,
|
80 |
+
"name": "get_company_profile",
|
81 |
+
"description": "get a company's profile information"
|
82 |
+
},
|
83 |
+
{
|
84 |
+
"function": FinnHubUtils.get_company_news,
|
85 |
+
"name": "get_company_news",
|
86 |
+
"description": "retrieve market news related to designated company"
|
87 |
+
},
|
88 |
+
{
|
89 |
+
"function": FinnHubUtils.get_basic_financials,
|
90 |
+
"name": "get_financial_basics",
|
91 |
+
"description": "get latest financial basics for a designated company"
|
92 |
+
},
|
93 |
+
{
|
94 |
+
"function": YFinanceUtils.get_stock_data,
|
95 |
+
"name": "get_stock_data",
|
96 |
+
"description": "retrieve stock price data for designated ticker symbol"
|
97 |
+
}
|
98 |
+
]
|
99 |
+
register_toolkits(tools, analyst, user_proxy)
|
100 |
+
|
101 |
+
def save_response_to_json(response, filename):
|
102 |
+
response_dict = {
|
103 |
+
"chat_id": response.chat_id,
|
104 |
+
"chat_history": response.chat_history,
|
105 |
+
"summary": response.summary,
|
106 |
+
"cost": response.cost,
|
107 |
+
"human_input": response.human_input
|
108 |
+
}
|
109 |
+
with open(filename, "w") as file:
|
110 |
+
file.write(json.dumps(response_dict, indent=4))
|
111 |
+
print(f"Response saved to {filename}")
|
112 |
+
|
113 |
+
# Function to initiate chat and save response
|
114 |
+
def initiate_chat_and_save_response(analyst, user_proxy, company):
|
115 |
+
today_date = get_current_date()
|
116 |
+
json_filename = f"result_{company}_{today_date}.json"
|
117 |
+
md_filename = f"result_{company}_{today_date}.md"
|
118 |
+
|
119 |
+
# Check if MD file already exists
|
120 |
+
if os.path.exists(md_filename):
|
121 |
+
return read_response_from_md(md_filename)
|
122 |
+
|
123 |
+
with Cache.disk() as cache:
|
124 |
+
response = user_proxy.initiate_chat(
|
125 |
+
analyst,
|
126 |
+
message=f"Use all the tools provided to retrieve information available for {company} upon {get_current_date()}. Analyze the positive developments and potential concerns of {company} with 2-4 most important factors respectively and keep them concise. Most factors should be inferred from company related news. Then make a rough prediction (e.g. up/down by %) of the {company} stock price movement for next week. Provide a summary analysis to support your prediction.",
|
127 |
+
cache=cache,
|
128 |
+
)
|
129 |
+
|
130 |
+
save_response_to_json(response, json_filename)
|
131 |
+
return json.dumps(response.chat_history, indent=4)
|
132 |
+
|
133 |
+
def filter_user_content(chat_history):
|
134 |
+
# 解析 chat_history 为 JSON 对象
|
135 |
+
chat_history_dict = json.loads(chat_history)
|
136 |
+
# 查找用户需要的内容
|
137 |
+
for entry in chat_history_dict:
|
138 |
+
if entry['role'] == 'user' and "###" in entry['content']:
|
139 |
+
return entry['content']
|
140 |
+
return "No relevant content found."
|
141 |
+
|
142 |
+
# 使用更新的函数在 analyze_company 中
|
143 |
+
def analyze_company(company):
|
144 |
+
if company:
|
145 |
+
company = company.upper()
|
146 |
+
today_date = get_current_date()
|
147 |
+
md_filename = f"result_{company}_{today_date}.md"
|
148 |
+
|
149 |
+
# Check if MD file already exists
|
150 |
+
if os.path.exists(md_filename):
|
151 |
+
return read_response_from_md(md_filename)
|
152 |
+
|
153 |
+
content = initiate_chat_and_save_response(analyst, user_proxy, company)
|
154 |
+
# 筛选有效的用户内容
|
155 |
+
filtered_content = filter_user_content(content)
|
156 |
+
save_to_md(filtered_content, md_filename) # 只保存筛选后的内容
|
157 |
+
return filtered_content
|
158 |
+
|
159 |
+
# 自定义CSS样式
|
160 |
+
custom_css = """
|
161 |
+
h1, h2, h3, h4, h5, h6 {
|
162 |
+
font-family: 'Arial', sans-serif;
|
163 |
+
font-weight: bold;
|
164 |
+
}
|
165 |
+
body {
|
166 |
+
font-family: 'Arial', sans-serif;
|
167 |
+
}
|
168 |
+
.gradio-container {
|
169 |
+
max-width: 800px;
|
170 |
+
margin: auto;
|
171 |
+
padding: 20px;
|
172 |
+
border: 1px solid #ccc;
|
173 |
+
border-radius: 10px;
|
174 |
+
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
175 |
+
}
|
176 |
+
textarea, input, .btn-primary {
|
177 |
+
font-size: 16px !important;
|
178 |
+
padding: 10px !important;
|
179 |
+
border-radius: 5px !important;
|
180 |
+
}
|
181 |
+
#component-0 > .wrap > .block.markdown-block > .markdown {
|
182 |
+
font-size: 24px !important;
|
183 |
+
line-height: 1.8 !important;
|
184 |
+
}
|
185 |
+
"""
|
186 |
+
|
187 |
+
# Gradio接口
|
188 |
+
iface = gr.Interface(
|
189 |
+
fn=analyze_company,
|
190 |
+
inputs=gr.Textbox(lines=1, placeholder="Enter company name or stock code"),
|
191 |
+
outputs=gr.Markdown(label="Trade-Helper"),
|
192 |
+
title="Trade-Helper",
|
193 |
+
description="Enter the company name or stock code to get a AI-Powered analysis and forcast prediction.",
|
194 |
+
css=custom_css,
|
195 |
+
allow_flagging='never'
|
196 |
+
)
|
197 |
+
|
198 |
+
if __name__ == "__main__":
|
199 |
+
iface.launch(share=True)
|
config_api_keys_sample
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Fill in your own api keys below, rename this file to config_api_keys and remove this line before you kick off.
|
2 |
+
{
|
3 |
+
"FINNHUB_API_KEY": "YOUR_FINNHUB_API_KEY",
|
4 |
+
"FMP_API_KEY": "YOUR_FMP_API_KEY",
|
5 |
+
"SEC_API_KEY": "YOUR_SEC_API_KEY"
|
6 |
+
}
|
configs/save_config_forecaster.json
ADDED
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"building_task": "Gather information like company profile, recent stock price fluctuations, market news, and financial basics of a specified company (e.g. AAPL) by programming and analyze its current positive developments and potential concerns. Based on all the information, come up with a rough estimate (e.g. up by 2-3%) and give a summary of the reasons for next week's stock price. Each python program should execute on its own, and avoid plotting any chart.",
|
3 |
+
"agent_configs": [
|
4 |
+
{
|
5 |
+
"name": "financial_data_analyst",
|
6 |
+
"model": "gpt-4",
|
7 |
+
"system_message": "You are now in a group chat. As a financial_data_analyst, you are tasked with particular responsibilities that will need your analytical and Python programming skills. Be ready to:\n\n 1. Make use of Python to gather relevant data. This could be information on specific companies, such as the company profile, recent stock price fluctuations, market news, and financial basics. The Python code should be able to output this necessary information. For instance, it may need to browse or search the web, download/read a file or print the content of a webpage or a file.\n\n 2. Use the gathered data to provide a well-informed analysis of the company. This may involve understanding current positive developments and potential concerns. You may also need to make a rough prediction of the next week's stock price (e.g., up by 2-3%) and provide an elaborative summary of the reasons. This task may necessitate the use of both your data analysis skills and your language proficiency to effectively convey the outcomes of your analysis.\n\n 3. Ensure clarity when using Python to solve a task, specifically by explaining your plan beforehand. Indicate when you will use code and when you will use your language skills. For example, the code may be used to extract financial data so that you can utilize your language skills for the analysis part.\n\n 4. Eliminate the necessity for the user to make changes to your code. Make sure your Python code is complete and doesn't call for modifications by the user. Also, ensure the code can run independently and avoid including anything that could prompt the user to copy and paste the result.\n\n 5. Be diligent in checking the execution results returned by the user. If there is an error, fix it, and if the error can't be fixed or the task isn't solved, think of a different approach. You can then output the full corrected code once again. \n\n 6. At the end of your tasks, remember to verify your answers with evidence where possible. When you are confident that your work is complete and correct, reply with \"TERMINATE.\"\n\nRemember, when puzzled, it's ok to communicate the issue to your group chat manager who will help guide you through or assign someone else to tackle the task.",
|
8 |
+
"description": "A financial data analyst is a specialized professional who collects, monitors, and creates reports from complex data sets to help a business in decision-making processes. This position should be filled by someone with strong analytical skills, a solid understanding of financial market trends, and proficiency in data analysis tools like Microsoft Excel or SQL. They should also have a keen attention to detail and the ability to communicate complicated data in understandable ways."
|
9 |
+
},
|
10 |
+
{
|
11 |
+
"name": "market_news_researcher",
|
12 |
+
"model": "gpt-4",
|
13 |
+
"system_message": "You are now in a group chat. Your task is to complete a market research study with other participants. As a market_news_researcher, you are expected to have strong skills in Python programming, financial analysis, market research, and the ability to analyze and interpret complex data.\n\n 1. Information Collection: Use your programming skills to gather relevant data. This may involve accessing financial data and market news from online sources, reading and downloading files, or retrieving the latest stock prices. Ascertain that the output of the data you need (for instance, company profile, stock price fluctuations, and market news about the specified company) is printed on your Python console. All required data should be gathered before attempting to solve the task based on your insights.\n \n 2. Task Execution: Use your programming skills to conduct necessary financial analyses, interpret complex financial data, and predict stock price movements. Make sure your Python code outputs the results of your analyses and provides a clear explanation of your findings. You should also specify your predictions for next week's stock price and provide a summary of the reasoning behind your predictions.\n \n 3. Planning: Clearly outline your plan before starting to perform your task. Communicate how you intend to collect and analyze the collected data, how you will program your Python code to predict future prices, and how you plan to execute your task step by step.\n \n 4. Error Handling: If you encounter an error when executing your Python code, rectify the error and provide the corrected Python code. Ensure that the modified Python code is complete and doesn't require the user to make adjustments. If the error persists, rethink your approach, gather additional data if necessary, and devise an alternative strategy.\n \n 5. Verification: After obtaining results, carefully verify your findings. If possible, provide supporting evidence in your response.\n \nThe conversation concludes when your research is successfully completed and the task has been fully accomplished. At this point, reply \"TERMINATE\". Before ending the conversation, you must ensure that the user is fully satisfied with your analysis and predictions. If you are unsure or confused, you are encouraged to ask the group chat manager for assistance and let them choose another participant.",
|
14 |
+
"description": "A market news researcher is a professional who stays updated with all the latest developments and trends in the market. They should possess strong analytical and research skills, along with a good understanding of business and finance. They should also have excellent communication skills to convincingly explain and debate about their findings and analysis in a group chat."
|
15 |
+
},
|
16 |
+
{
|
17 |
+
"name": "python_programmer",
|
18 |
+
"model": "gpt-4",
|
19 |
+
"system_message": "You are now in a group chat. You have been assigned a task along with other participants. As a Python programmer, your primary role is to program solutions for data collection, analysis, and estimation tasks related to a specified company's stock.\n\nWhen there is a need to gather information, such as a company's profile, stock price history, market news, and basic financials, utilize your coding skills to achieve this. Use python code to browse or search the web, download or read relevant files, or pull the desired data from financial websites. Instead of relying on others in the group to provide this information, your code should independently fetch it. Always use a single piece of robust, comprehensive code that the users can run without needing to modify anything.\n\nWith the collected data, analyze the current positive developments and potential concerns of the specified company. Use python programs to automate your data analysis where possible and compile your observations and inferences into a clear and concise summary.\n\nBased on the data and your analysis, develop a predictive model using Python to estimate the stock's price change for the next week. Explain your model's rationale and output a numerical estimate, e.g., \"up by 2-3%\". All code should be designed to execute as standalone programs and should avoid the use of charts or visualizations.\n\nOnce your code has achieved the task at hand, carefully verify its output against reliable sources. Include any evidence or references that support your findings.\n\nWhen encountering problems or uncertainties, engage with the group chat and ask for assistance from the group chat manager. Similarly, if a code execution returns errors or does not solve the task satisfactorily, diagnose the issue, correct your assumptions, gather additional necessary information and generate a new approach with python programs.\n\nUpon completion of your tasks, ensuring the satisfaction of user's needs and the correctness of your solutions, respond with \"TERMINATE\".",
|
20 |
+
"description": "A Python programmer is a highly skilled individual with expertise in Python, who can write, test, debug and improve Python code. They possess the ability to detect critical programming and logic errors, thereby contributing effectively to group discussions by providing accurate answers or code corrections. They require strong problem-solving skills, a firm understanding of data structures, algorithms, and software development principles in the Python language."
|
21 |
+
},
|
22 |
+
{
|
23 |
+
"name": "stock_price_estimator",
|
24 |
+
"model": "gpt-4",
|
25 |
+
"system_message": "You are a diligent member of a team focused on stock price estimation. Using your knowledge on data sourcing and analytical skills, you will aid in gathering essential company information through programming in Python. Conduct thorough research on specific company profiles (e.g., AAPL), monitoring recent stock price fluctuations, keeping up-to-date with market news, and understanding financial fundamentals. \n\nBased on your analysis, you will aim to predict the next week's stock price with a rough estimate (e.g., up 2-3%). Notably, your role involves creating and executing standalone python programs, generating summary explanations of stock trend reasons without resorting to graphical chart representations.\n\nCarefully, step-by-step:\n\n1. Employ your Python coding skills to gather necessary information. You can accomplish this by scripting to search the web, download/read and print the content of files, pages, current time/date, and check the operating system specifics. Execute your scripts and share the output.\n\n2. Utilize your analytical language skills to prepare comprehensive reports detailing positive developments and potential concerns based on the data available.\n\n3. Use your attention to detail to meticulously check the execution results given by other team members. If there is an error, identify and correct it, then output the comprehensive and correct Python program. In case the analysis is not complete or an error is unfixable with the current data or approach, notify the team, gather additional information as needed, and propose a different method.\n\n4. After successfully analysing and estimating a stock\u2019s price movement, ensure to verify your estimations carefully. If possible, include convincing evidence in your report.\n\n5. Instigate the termination of a task phase once you're confident the task is complete, knowing that the final decision rests with the group chat manager. \n\n6. If stumped or uncertain, refrain from hesitation. Seek guidance from the group chat manager who will direct another participant to aid you.\n\nBear in mind; coding skills are limited to Python. You are encouraged to doubt previous messages or codes in the group chat and provide corrected code if there is no output after execution. Engage in healthy team communication, negotiate doubts and difficulties, and seek assistance when needed.",
|
26 |
+
"description": "A stock price estimator is a financial expert who specializes in financial modeling, data analysis and has a profound understanding of financial markets. The incumbent should have strong skills in Python for analyzing data and building prediction models, with the ability to scrutinize previous messages or code in a group chat and provide revisions if uncertainties appear. Familiarity with statistical modeling tools and concepts, such as regression, time-series analysis, as well as machine learning techniques, are substantial for this role."
|
27 |
+
},
|
28 |
+
{
|
29 |
+
"name": "financial_report_writer",
|
30 |
+
"model": "gpt-4",
|
31 |
+
"system_message": "You are now in a group chat. Your role is to complete a task with other participants. As a financial report writer specialized in analyzing company stock performances, you will use your Python programming skills to collect, analyze, and summarize relevant data pertaining to a specified company's market position and stock price trends. \n\nYou are expected to perform the following functions:\n\n 1. Gather necessary information such as a company's profile, recent stock price fluctuations, market news, and financial basics about the company. You will leverage your Python programming skills to develop codes that download, read, and print the desired content from the web.\n\n 2. After collecting the relevant information, use your financial analytical skills to evaluate the company's current positive developments and potential concerns. Your evaluation should be based on the data you have gathered.\n\n 3. When your analysis is complete, give a rough estimate (e.g., up by 2-3%) for the next week's stock price. You should also provide a summary of the reasons for your prediction.\n\n 4. Make sure every Python program you create will execute on its own. Avoid designing programs that require the user to modify your code. Each Python program you suggest should not involve chart plotting.\n\n 5. If an error occurs while running the code, you should correct the error and output the corrected code again. If the error can't be fixed or if the task isn't solved even after the code runs successfully, use your analytical skills to identify the problem and come up with an alternative approach.\n\n 6. Verify your answer carefully before presenting it. Include evidence to validate your findings, where possible.\n\n 7. If you face any confusion during the task, seek assistance from the group chat manager. If you think your task is complete and satisfactory, please reply \"TERMINATE\".",
|
32 |
+
"description": "A financial report writer is a professional with strong analytical skills, expertise in financial data analysis and capable of creating concise, accurate reports. This position requires proficiency in financial software, a keen understanding of financial concepts and excellent writing skills to effectively compile and present financial data. They should also possess effective communication skills and the ability to question and clarify any doubtful financial data or information shared in the group chat."
|
33 |
+
}
|
34 |
+
],
|
35 |
+
"coding": true,
|
36 |
+
"default_llm_config": {
|
37 |
+
"temperature": 0
|
38 |
+
},
|
39 |
+
"code_execution_config": {
|
40 |
+
"work_dir": "coding",
|
41 |
+
"use_docker": false
|
42 |
+
}
|
43 |
+
}
|
experiments/investment_group.json
ADDED
@@ -0,0 +1,188 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"CIO": {
|
3 |
+
"role": "Chief Investment Officer",
|
4 |
+
"responsibilities": [
|
5 |
+
"Oversee the entire investment analysis process.",
|
6 |
+
"Integrate insights from various groups.",
|
7 |
+
"Make the final decision on portfolio composition and adjustments."
|
8 |
+
]
|
9 |
+
},
|
10 |
+
"groups": {
|
11 |
+
"Market Sentiment Analysts": {
|
12 |
+
"responsibilities": [
|
13 |
+
"Track and interpret market trends and news.",
|
14 |
+
"Analyze social media, news articles, and market reports for market sentiment.",
|
15 |
+
"Provide insights on market sentiment and its potential impact on investments."
|
16 |
+
],
|
17 |
+
"with_leader": {
|
18 |
+
"leader": {
|
19 |
+
"title": "Senior Market Sentiment Analyst",
|
20 |
+
"responsibilities": [
|
21 |
+
"Oversee the collection and analysis of market sentiment data.",
|
22 |
+
"Guide and coordinate the work of the team.",
|
23 |
+
"Present findings to the CIO."
|
24 |
+
]
|
25 |
+
},
|
26 |
+
"employees": [
|
27 |
+
{
|
28 |
+
"title": "Market Sentiment Analyst",
|
29 |
+
"responsibilities": [
|
30 |
+
"Track and interpret market trends and news.",
|
31 |
+
"Analyze social media and news articles for market sentiment."
|
32 |
+
]
|
33 |
+
},
|
34 |
+
{
|
35 |
+
"title": "Junior Market Sentiment Analyst",
|
36 |
+
"responsibilities": [
|
37 |
+
"Assist in data collection and preliminary analysis.",
|
38 |
+
"Support the senior analyst in preparing reports."
|
39 |
+
]
|
40 |
+
}
|
41 |
+
]
|
42 |
+
},
|
43 |
+
"without_leader": {
|
44 |
+
"employees": [
|
45 |
+
{
|
46 |
+
"title": "Market Sentiment Analyst",
|
47 |
+
"responsibilities": [
|
48 |
+
"Track and interpret market trends and news.",
|
49 |
+
"Analyze social media and news articles for market sentiment."
|
50 |
+
]
|
51 |
+
},
|
52 |
+
{
|
53 |
+
"title": "Market Sentiment Analyst",
|
54 |
+
"responsibilities": [
|
55 |
+
"Conduct sentiment analysis and contribute to reports.",
|
56 |
+
"Collaborate with peers to ensure comprehensive coverage."
|
57 |
+
]
|
58 |
+
},
|
59 |
+
{
|
60 |
+
"title": "Market Sentiment Analyst",
|
61 |
+
"responsibilities": [
|
62 |
+
"Gather and process data on market sentiment.",
|
63 |
+
"Collaborate with peers on analysis and reporting."
|
64 |
+
]
|
65 |
+
}
|
66 |
+
]
|
67 |
+
}
|
68 |
+
},
|
69 |
+
"Risk Assessment Analysts": {
|
70 |
+
"responsibilities": [
|
71 |
+
"Identify and quantify potential risks in the portfolio.",
|
72 |
+
"Develop risk assessment models and tools.",
|
73 |
+
"Monitor and report on risk exposure.",
|
74 |
+
"Propose risk mitigation strategies."
|
75 |
+
],
|
76 |
+
"with_leader": {
|
77 |
+
"leader": {
|
78 |
+
"title": "Senior Risk Analyst",
|
79 |
+
"responsibilities": [
|
80 |
+
"Oversee risk assessment and management activities.",
|
81 |
+
"Guide and coordinate the work of the team.",
|
82 |
+
"Present findings to the CIO."
|
83 |
+
]
|
84 |
+
},
|
85 |
+
"employees": [
|
86 |
+
{
|
87 |
+
"title": "Risk Analyst",
|
88 |
+
"responsibilities": [
|
89 |
+
"Identify and quantify potential risks in the portfolio.",
|
90 |
+
"Develop risk assessment models."
|
91 |
+
]
|
92 |
+
},
|
93 |
+
{
|
94 |
+
"title": "Junior Risk Analyst",
|
95 |
+
"responsibilities": [
|
96 |
+
"Assist in data collection and preliminary risk analysis.",
|
97 |
+
"Support the senior analyst in preparing reports."
|
98 |
+
]
|
99 |
+
}
|
100 |
+
]
|
101 |
+
},
|
102 |
+
"without_leader": {
|
103 |
+
"employees": [
|
104 |
+
{
|
105 |
+
"title": "Risk Analyst",
|
106 |
+
"responsibilities": [
|
107 |
+
"Identify and quantify potential risks in the portfolio.",
|
108 |
+
"Develop risk assessment models."
|
109 |
+
]
|
110 |
+
},
|
111 |
+
{
|
112 |
+
"title": "Risk Analyst",
|
113 |
+
"responsibilities": [
|
114 |
+
"Conduct risk analysis and contribute to risk reports.",
|
115 |
+
"Collaborate with peers to ensure comprehensive risk coverage."
|
116 |
+
]
|
117 |
+
},
|
118 |
+
{
|
119 |
+
"title": "Risk Analyst",
|
120 |
+
"responsibilities": [
|
121 |
+
"Gather and process risk-related data.",
|
122 |
+
"Collaborate with peers on risk assessment and mitigation strategies."
|
123 |
+
]
|
124 |
+
}
|
125 |
+
]
|
126 |
+
}
|
127 |
+
},
|
128 |
+
"Fundamental Analysts": {
|
129 |
+
"responsibilities": [
|
130 |
+
"Review and interpret company financial statements.",
|
131 |
+
"Summarize key financial metrics and trends.",
|
132 |
+
"Provide forecasts and financial health assessments.",
|
133 |
+
"Collaborate with data scientists for deeper insights."
|
134 |
+
],
|
135 |
+
"with_leader": {
|
136 |
+
"leader": {
|
137 |
+
"title": "Senior Fundamental Analyst",
|
138 |
+
"responsibilities": [
|
139 |
+
"Oversee the analysis of financial statements and annual reports.",
|
140 |
+
"Guide and coordinate the work of the team.",
|
141 |
+
"Present findings to the CIO."
|
142 |
+
]
|
143 |
+
},
|
144 |
+
"employees": [
|
145 |
+
{
|
146 |
+
"title": "Fundamental Analyst",
|
147 |
+
"responsibilities": [
|
148 |
+
"Review and interpret company financial statements.",
|
149 |
+
"Summarize key financial metrics and trends."
|
150 |
+
]
|
151 |
+
},
|
152 |
+
{
|
153 |
+
"title": "Junior Fundamental Analyst",
|
154 |
+
"responsibilities": [
|
155 |
+
"Assist in data collection and preliminary financial analysis.",
|
156 |
+
"Support the senior analyst in preparing reports."
|
157 |
+
]
|
158 |
+
}
|
159 |
+
]
|
160 |
+
},
|
161 |
+
"without_leader": {
|
162 |
+
"employees": [
|
163 |
+
{
|
164 |
+
"title": "Fundamental Analyst",
|
165 |
+
"responsibilities": [
|
166 |
+
"Review and interpret company financial statements.",
|
167 |
+
"Summarize key financial metrics and trends."
|
168 |
+
]
|
169 |
+
},
|
170 |
+
{
|
171 |
+
"title": "Fundamental Analyst",
|
172 |
+
"responsibilities": [
|
173 |
+
"Conduct financial analysis and contribute to reports.",
|
174 |
+
"Collaborate with peers to ensure thorough analysis."
|
175 |
+
]
|
176 |
+
},
|
177 |
+
{
|
178 |
+
"title": "Fundamental Analyst",
|
179 |
+
"responsibilities": [
|
180 |
+
"Gather and process financial data.",
|
181 |
+
"Collaborate with peers on financial analysis and reporting."
|
182 |
+
]
|
183 |
+
}
|
184 |
+
]
|
185 |
+
}
|
186 |
+
}
|
187 |
+
}
|
188 |
+
}
|
experiments/multi_factor_agents.py
ADDED
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import re
|
2 |
+
import json
|
3 |
+
import autogen
|
4 |
+
from autogen.cache import Cache
|
5 |
+
|
6 |
+
# from finrobot.utils import create_inner_assistant
|
7 |
+
|
8 |
+
from functools import partial
|
9 |
+
|
10 |
+
|
11 |
+
config_list_gpt4 = autogen.config_list_from_json(
|
12 |
+
"OAI_CONFIG_LIST",
|
13 |
+
filter_dict={
|
14 |
+
"model": ["gpt-4-0125-preview"],
|
15 |
+
},
|
16 |
+
)
|
17 |
+
|
18 |
+
llm_config = {
|
19 |
+
"config_list": config_list_gpt4,
|
20 |
+
"cache_seed": 42,
|
21 |
+
"temperature": 0,
|
22 |
+
}
|
23 |
+
|
24 |
+
quant_group_config = json.load(open("quantitative_investment_group_config.json"))
|
25 |
+
|
26 |
+
# user_proxy = autogen.UserProxyAgent(
|
27 |
+
# name="User",
|
28 |
+
# # human_input_mode="ALWAYS",
|
29 |
+
# human_input_mode="NEVER",
|
30 |
+
# code_execution_config=False
|
31 |
+
# )
|
32 |
+
|
33 |
+
group_descs = "\n\n".join(
|
34 |
+
[
|
35 |
+
"Name: {} \nResponsibility: {}".format(c["name"], c["profile"])
|
36 |
+
for c in quant_group_config
|
37 |
+
]
|
38 |
+
)
|
39 |
+
|
40 |
+
group_leader = autogen.AssistantAgent(
|
41 |
+
name="Group_Leader",
|
42 |
+
system_message="""
|
43 |
+
As a group leader, you are responsible for coordinating the team's efforts to achieve the project's objectives.
|
44 |
+
You must ensure that the team is working together effectively and efficiently.
|
45 |
+
Summarize the status of the whole project progess every time you respond, and assign task to one of the group members to progress the project.
|
46 |
+
Orders should follow the format: \"[<name of staff>] <order>\" and appear at the end of your response.
|
47 |
+
After receiving feedback from the team members, check the progress of the task, and make sure the task is well completed before proceding to th next order.
|
48 |
+
If the task is not well completed, your order should be to provide assistance and guidance for the team members to complete it again.
|
49 |
+
Reply TERMINATE only when the whole project is done. Your team members are as follows:\n\n
|
50 |
+
"""
|
51 |
+
+ group_descs,
|
52 |
+
llm_config=llm_config,
|
53 |
+
)
|
54 |
+
|
55 |
+
executor = autogen.UserProxyAgent(
|
56 |
+
name="Executor",
|
57 |
+
human_input_mode="NEVER",
|
58 |
+
# human_input_mode="ALWAYS",
|
59 |
+
is_termination_msg=lambda x: x.get("content", "")
|
60 |
+
and "TERMINATE" in x.get("content", ""),
|
61 |
+
# max_consecutive_auto_reply=3,
|
62 |
+
code_execution_config={
|
63 |
+
"last_n_messages": 3,
|
64 |
+
"work_dir": "quant",
|
65 |
+
"use_docker": False,
|
66 |
+
},
|
67 |
+
)
|
68 |
+
|
69 |
+
quant_group = {
|
70 |
+
c["name"]: autogen.agentchat.AssistantAgent(
|
71 |
+
name=c["name"],
|
72 |
+
system_message=c["profile"],
|
73 |
+
llm_config=llm_config,
|
74 |
+
)
|
75 |
+
for c in quant_group_config
|
76 |
+
}
|
77 |
+
|
78 |
+
|
79 |
+
def order_trigger(pattern, sender):
|
80 |
+
# print(pattern)
|
81 |
+
# print(sender.last_message()['content'])
|
82 |
+
return pattern in sender.last_message()["content"]
|
83 |
+
|
84 |
+
|
85 |
+
def order_message(pattern, recipient, messages, sender, config):
|
86 |
+
full_order = recipient.chat_messages_for_summary(sender)[-1]["content"]
|
87 |
+
pattern = rf"\[{pattern}\](?::)?\s*(.+?)(?=\n\[|$)"
|
88 |
+
match = re.search(pattern, full_order, re.DOTALL)
|
89 |
+
if match:
|
90 |
+
order = match.group(1).strip()
|
91 |
+
else:
|
92 |
+
order = full_order
|
93 |
+
return f"""
|
94 |
+
Follow leader's order and complete the following task: {order}.
|
95 |
+
For coding tasks, provide python scripts and executor will run it for you.
|
96 |
+
Save your results or any intermediate data locally and let group leader know how to read them.
|
97 |
+
DO NOT include TERMINATE in your response until you have received the results from the execution of the Python scripts.
|
98 |
+
If the task cannot be done currently or need assistance from other members, report the reasons or requirements to group leader ended with TERMINATE.
|
99 |
+
"""
|
100 |
+
# For coding tasks, only use the functions you have been provided with.
|
101 |
+
|
102 |
+
|
103 |
+
for name, agent in quant_group.items():
|
104 |
+
executor.register_nested_chats(
|
105 |
+
[
|
106 |
+
{
|
107 |
+
"sender": executor,
|
108 |
+
"recipient": agent,
|
109 |
+
"message": partial(order_message, name),
|
110 |
+
"summary_method": "reflection_with_llm",
|
111 |
+
"max_turns": 10,
|
112 |
+
"max_consecutive_auto_reply": 3,
|
113 |
+
}
|
114 |
+
],
|
115 |
+
trigger=partial(order_trigger, f"[{name}]"),
|
116 |
+
)
|
117 |
+
|
118 |
+
quant_task = "Develop and test the feasibility of a quantitative investment strategy focusing on the Dow Jones 30 stocks, utilizing your multi-factor analysis expertise to identify potential investment opportunities and optimize the portfolio's performance. Ensure the strategy is robust, data-driven, and aligns with our risk management principles."
|
119 |
+
|
120 |
+
with Cache.disk() as cache:
|
121 |
+
executor.initiate_chat(group_leader, message=quant_task, cache=cache)
|
experiments/portfolio_optimization.py
ADDED
@@ -0,0 +1,312 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import re
|
2 |
+
import json
|
3 |
+
import autogen
|
4 |
+
from autogen.cache import Cache
|
5 |
+
|
6 |
+
# from finrobot.utils import create_inner_assistant
|
7 |
+
|
8 |
+
from functools import partial
|
9 |
+
from textwrap import dedent
|
10 |
+
|
11 |
+
|
12 |
+
llm_config = {
|
13 |
+
"config_list": autogen.config_list_from_json(
|
14 |
+
"../OAI_CONFIG_LIST",
|
15 |
+
filter_dict={
|
16 |
+
"model": ["gpt-4-0125-preview"],
|
17 |
+
},
|
18 |
+
),
|
19 |
+
"cache_seed": 42,
|
20 |
+
"temperature": 0,
|
21 |
+
}
|
22 |
+
|
23 |
+
leader_system_message = dedent(
|
24 |
+
"""
|
25 |
+
You are the leader of the following group members:
|
26 |
+
|
27 |
+
{group_desc}
|
28 |
+
|
29 |
+
As a group leader, you are responsible for coordinating the team's efforts to achieve the project's objectives. You must ensure that the team is working together effectively and efficiently.
|
30 |
+
|
31 |
+
Summarize the status of the whole project progess every time you respond, and assign task to one of the group members to progress the project.
|
32 |
+
|
33 |
+
Orders should follow the format: \"[<name of staff>] <order>\" and appear at the end of your response.
|
34 |
+
|
35 |
+
After receiving feedback from the team members, check the progress of the task, and make sure the task is well completed before proceding to th next order.
|
36 |
+
|
37 |
+
Reply "TERMINATE" in the end when everything is done.
|
38 |
+
"""
|
39 |
+
)
|
40 |
+
role_system_message = dedent(
|
41 |
+
"""
|
42 |
+
As a {title}, your reponsibilities are as follows:
|
43 |
+
{responsibilities}
|
44 |
+
|
45 |
+
Reply "TERMINATE" in the end when everything is done.
|
46 |
+
"""
|
47 |
+
)
|
48 |
+
order_template = dedent(
|
49 |
+
"""
|
50 |
+
Follow leader's order and complete the following task with your group members:
|
51 |
+
|
52 |
+
{order}
|
53 |
+
|
54 |
+
For coding tasks, provide python scripts and executor will run it for you.
|
55 |
+
Save your results or any intermediate data locally and let group leader know how to read them.
|
56 |
+
DO NOT include "TERMINATE" in your response until you have received the results from the execution of the Python scripts.
|
57 |
+
If the task cannot be done currently or need assistance from other members, report the reasons or requirements to group leader ended with TERMINATE.
|
58 |
+
"""
|
59 |
+
)
|
60 |
+
|
61 |
+
|
62 |
+
def order_trigger(sender, name, pattern):
|
63 |
+
# print(pattern)
|
64 |
+
# print(sender.name)
|
65 |
+
return sender.name == name and pattern in sender.last_message()["content"]
|
66 |
+
|
67 |
+
|
68 |
+
def order_message(pattern, recipient, messages, sender, config):
|
69 |
+
full_order = recipient.chat_messages_for_summary(sender)[-1]["content"]
|
70 |
+
pattern = rf"\[{pattern}\](?::)?\s*(.+?)(?=\n\[|$)"
|
71 |
+
match = re.search(pattern, full_order, re.DOTALL)
|
72 |
+
if match:
|
73 |
+
order = match.group(1).strip()
|
74 |
+
else:
|
75 |
+
order = full_order
|
76 |
+
return order_template.format(order=order)
|
77 |
+
|
78 |
+
|
79 |
+
group_config = json.load(open("investment_group.json"))
|
80 |
+
|
81 |
+
user_proxy = autogen.UserProxyAgent(
|
82 |
+
name="User",
|
83 |
+
# human_input_mode="ALWAYS",
|
84 |
+
human_input_mode="NEVER",
|
85 |
+
is_termination_msg=lambda x: x.get("content", "")
|
86 |
+
and "TERMINATE" in x.get("content", ""),
|
87 |
+
code_execution_config={
|
88 |
+
"last_n_messages": 3,
|
89 |
+
"work_dir": "quant",
|
90 |
+
"use_docker": False,
|
91 |
+
},
|
92 |
+
)
|
93 |
+
|
94 |
+
|
95 |
+
def concat(responsiblities):
|
96 |
+
return "\n".join([f" - {r}" for r in responsiblities])
|
97 |
+
|
98 |
+
|
99 |
+
with_leader_config = {
|
100 |
+
"Market Sentiment Analysts": True,
|
101 |
+
"Risk Assessment Analysts": True,
|
102 |
+
"Fundamental Analysts": True,
|
103 |
+
}
|
104 |
+
|
105 |
+
representatives = []
|
106 |
+
|
107 |
+
for group_name, single_group_config in group_config["groups"].items():
|
108 |
+
|
109 |
+
with_leader = with_leader_config.get(group_name)
|
110 |
+
if with_leader:
|
111 |
+
group_members = single_group_config["with_leader"]
|
112 |
+
leader_config = group_members["leader"]
|
113 |
+
else:
|
114 |
+
group_members = single_group_config["without_leader"]
|
115 |
+
leader_config = None
|
116 |
+
|
117 |
+
employees = group_members["employees"]
|
118 |
+
need_suffix = len(set([c["title"] for c in employees])) == 1
|
119 |
+
employee_agents = {}
|
120 |
+
for i, c in enumerate(employees):
|
121 |
+
name = c["title"] + (f"_{i+1}" if need_suffix else "")
|
122 |
+
employee_agents[name] = autogen.AssistantAgent(
|
123 |
+
name=name,
|
124 |
+
system_message=role_system_message.format(
|
125 |
+
title=c["title"], responsibilities=concat(c["responsibilities"])
|
126 |
+
),
|
127 |
+
llm_config=llm_config,
|
128 |
+
)
|
129 |
+
|
130 |
+
if leader_config:
|
131 |
+
group_descs = "\n\n".join(
|
132 |
+
[
|
133 |
+
"Name: {}\nResponsibility:\n{}".format(
|
134 |
+
c["title"] + (f"_{i+1}" if need_suffix else ""),
|
135 |
+
concat(c["responsibilities"]),
|
136 |
+
)
|
137 |
+
for i, c in enumerate(employees)
|
138 |
+
]
|
139 |
+
)
|
140 |
+
role_prompt = role_system_message.format(
|
141 |
+
title=leader_config["title"],
|
142 |
+
responsibilities=concat(leader_config["responsibilities"]),
|
143 |
+
)
|
144 |
+
leader_prompt = leader_system_message.format(group_desc=group_descs)
|
145 |
+
leader = autogen.AssistantAgent(
|
146 |
+
name=leader_config["title"],
|
147 |
+
system_message=role_prompt + "\n\n" + leader_prompt,
|
148 |
+
llm_config=llm_config,
|
149 |
+
)
|
150 |
+
|
151 |
+
for name, agent in employee_agents.items():
|
152 |
+
user_proxy.register_nested_chats(
|
153 |
+
[
|
154 |
+
{
|
155 |
+
"sender": user_proxy,
|
156 |
+
"recipient": agent,
|
157 |
+
"message": partial(order_message, name),
|
158 |
+
"summary_method": "reflection_with_llm",
|
159 |
+
"max_turns": 10,
|
160 |
+
"max_consecutive_auto_reply": 3,
|
161 |
+
}
|
162 |
+
],
|
163 |
+
trigger=partial(order_trigger, name=leader.name, pattern=f"[{name}]"),
|
164 |
+
)
|
165 |
+
representatives.append(
|
166 |
+
{
|
167 |
+
"name": leader_config["title"],
|
168 |
+
"responsibilities": concat(single_group_config["responsibilities"]),
|
169 |
+
"agent": leader,
|
170 |
+
}
|
171 |
+
)
|
172 |
+
|
173 |
+
else:
|
174 |
+
group_chat = autogen.GroupChat(
|
175 |
+
list(employee_agents.values()) + [user_proxy],
|
176 |
+
messages=[],
|
177 |
+
speaker_selection_method="round_robin",
|
178 |
+
)
|
179 |
+
manager = autogen.GroupChatManager(
|
180 |
+
group_chat, name=group_name, llm_config=llm_config
|
181 |
+
)
|
182 |
+
representatives.append(
|
183 |
+
{
|
184 |
+
"name": group_name,
|
185 |
+
"responsibilities": concat(single_group_config["responsibilities"]),
|
186 |
+
"agent": manager,
|
187 |
+
}
|
188 |
+
)
|
189 |
+
|
190 |
+
|
191 |
+
cio_config = group_config["CIO"]
|
192 |
+
|
193 |
+
role_prompt = role_system_message.format(
|
194 |
+
title=cio_config["role"], responsibilities=concat(cio_config["responsibilities"])
|
195 |
+
)
|
196 |
+
group_descs = "\n\n".join(
|
197 |
+
[
|
198 |
+
"Name: {}\nResponsibility:\n{}".format(
|
199 |
+
c["name"],
|
200 |
+
c["responsibilities"],
|
201 |
+
)
|
202 |
+
for c in representatives
|
203 |
+
]
|
204 |
+
)
|
205 |
+
leader_prompt = leader_system_message.format(group_desc=group_descs)
|
206 |
+
|
207 |
+
cio = autogen.AssistantAgent(
|
208 |
+
name=cio_config["role"],
|
209 |
+
system_message=role_prompt + "\n\n" + leader_prompt,
|
210 |
+
llm_config=llm_config,
|
211 |
+
)
|
212 |
+
|
213 |
+
for agent_config in representatives:
|
214 |
+
name = agent_config["name"]
|
215 |
+
user_proxy.register_nested_chats(
|
216 |
+
[
|
217 |
+
{
|
218 |
+
"sender": user_proxy,
|
219 |
+
"recipient": agent_config["agent"],
|
220 |
+
"message": partial(order_message, name),
|
221 |
+
"summary_method": "reflection_with_llm",
|
222 |
+
"max_turns": 10,
|
223 |
+
"max_consecutive_auto_reply": 3,
|
224 |
+
}
|
225 |
+
],
|
226 |
+
trigger=partial(order_trigger, name=cio.name, pattern=f"[{name}]"),
|
227 |
+
)
|
228 |
+
|
229 |
+
task = dedent(
|
230 |
+
"""
|
231 |
+
As the Chief Investment Officer, your task is to optimize our current investment portfolio based on the latest annual reports. This will involve coordinating with the Market Sentiment Analysts, Risk Assessment Analysts, and Fundamental Analysts to gather and analyze the relevant data. The goal is to ensure our portfolio is well-positioned for growth while managing risks effectively.
|
232 |
+
|
233 |
+
Specific Steps:
|
234 |
+
|
235 |
+
[Review New Annual Reports]:
|
236 |
+
Collect the latest annual reports from companies within our portfolio and potential new investments.
|
237 |
+
|
238 |
+
[Coordinate with Market Sentiment Analysts]:
|
239 |
+
Task: Analyze the market sentiment for these companies based on recent trends and news.
|
240 |
+
Deliverable: Provide a comprehensive report on market sentiment and investor behavior for each company.
|
241 |
+
|
242 |
+
[Coordinate with Risk Assessment Analysts]:
|
243 |
+
Task: Evaluate the risks associated with each company and their sectors.
|
244 |
+
Deliverable: Develop a risk assessment report highlighting potential risks and proposed mitigation strategies.
|
245 |
+
|
246 |
+
[Coordinate with Fundamental Analysts]:
|
247 |
+
Task: Perform a detailed financial analysis of each company, including key financial metrics, trends, and forecasts.
|
248 |
+
Deliverable: Provide a summary of financial health and growth prospects for each company.
|
249 |
+
|
250 |
+
[Integrate Findings]:
|
251 |
+
Combine insights from all three groups to get a holistic view of each company’s potential.
|
252 |
+
Evaluate how these insights impact our current portfolio and identify any necessary adjustments.
|
253 |
+
|
254 |
+
[Optimize Portfolio]:
|
255 |
+
Based on the integrated analysis, recommend adjustments to the portfolio.
|
256 |
+
Ensure the portfolio is balanced, with an optimal mix of high-growth, stable, and low-risk investments.
|
257 |
+
|
258 |
+
[Report and Implement]:
|
259 |
+
Present your final recommendations and the rationale behind them.
|
260 |
+
Oversee the implementation of the approved portfolio adjustments.
|
261 |
+
|
262 |
+
[Expected Outcome]:
|
263 |
+
A revised portfolio that leverages the latest financial insights and market sentiment to maximize growth and manage risks effectively.
|
264 |
+
|
265 |
+
If you have any questions or need additional resources, please let me know.
|
266 |
+
"""
|
267 |
+
)
|
268 |
+
|
269 |
+
task = dedent(
|
270 |
+
"""
|
271 |
+
As the Chief Investment Officer, your task is to evaluate the potential investment in Company ABC based on the provided data. You will need to coordinate with the Market Sentiment Analysts, Risk Assessment Analysts, and Fundamental Analysts to gather and analyze the relevant information. Your final deliverable should include a comprehensive evaluation and a recommendation on whether to invest in Company ABC.
|
272 |
+
|
273 |
+
Specific Instructions:
|
274 |
+
|
275 |
+
Coordinate with Market Sentiment Analysts:
|
276 |
+
|
277 |
+
Task: Calculate the sentiment score based on the provided market sentiment data.
|
278 |
+
Data: Positive mentions (80), Negative mentions (20)
|
279 |
+
Formula: Sentiment Score = (Positive Mentions - Negative Mentions) / Total Mentions
|
280 |
+
Expected Output: Sentiment Score (percentage)
|
281 |
+
|
282 |
+
Coordinate with Risk Assessment Analysts:
|
283 |
+
|
284 |
+
Task: Calculate the risk score using the provided financial ratios.
|
285 |
+
Data:
|
286 |
+
Debt-to-Equity Ratio: 1.5
|
287 |
+
Current Ratio: 2.0
|
288 |
+
Return on Equity (ROE): 0.1 (10%)
|
289 |
+
Weights: Debt-to-Equity (0.5), Current Ratio (0.3), ROE (0.2)
|
290 |
+
Formula: Risk Score = 0.5 * Debt-to-Equity + 0.3 * (1 / Current Ratio) - 0.2 * ROE
|
291 |
+
Expected Output: Risk Score
|
292 |
+
|
293 |
+
Coordinate with Fundamental Analysts:
|
294 |
+
|
295 |
+
Task: Calculate the Profit Margin and Return on Assets (ROA) based on the provided financial data.
|
296 |
+
Data:
|
297 |
+
Revenue: $1,000,000
|
298 |
+
Net Income: $100,000
|
299 |
+
Total Assets: $500,000
|
300 |
+
Formulas:
|
301 |
+
Profit Margin = (Net Income / Revenue) * 100
|
302 |
+
ROA = (Net Income / Total Assets) * 100
|
303 |
+
Expected Outputs: Profit Margin (percentage) and ROA (percentage)
|
304 |
+
|
305 |
+
Final Deliverable:
|
306 |
+
Integrate Findings: Compile the insights from all three groups to get a holistic view of Company ABC's potential.
|
307 |
+
Evaluation and Recommendation: Based on the integrated analysis, provide a recommendation on whether to invest in Company ABC, including the rationale behind your decision.
|
308 |
+
"""
|
309 |
+
)
|
310 |
+
|
311 |
+
with Cache.disk() as cache:
|
312 |
+
user_proxy.initiate_chat(cio, message=task, cache=cache)
|
experiments/quantitative_investment_group_config.json
ADDED
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
[
|
2 |
+
{
|
3 |
+
"name": "Value_Factor_Researcher",
|
4 |
+
"profile": "As a value factor researcher, the individual must possess expertise in financial statement analysis, a strong understanding of valuation metrics, adeptness in Python for quantitative modeling, and the ability to work collaboratively in team settings to integrate the value perspective into broader investment strategies."
|
5 |
+
},
|
6 |
+
{
|
7 |
+
"name": "Growth_Factor_Researcher",
|
8 |
+
"profile": "As a growth factor researcher, the individual must possess expertise in analyzing corporate growth indicators like earnings and revenue expansion, have strong Python skills for data analysis, and collaborate effectively in group settings to evaluate investment growth opportunities."
|
9 |
+
},
|
10 |
+
{
|
11 |
+
"name": "Momentum_Factor_Researcher",
|
12 |
+
"profile": "As a momentum factor researcher, one needs to have the ability to identify and analyze market trends and price patterns, be proficient in Python for statistical analysis, and work collaboratively in a team to leverage momentum-based investment strategies."
|
13 |
+
},
|
14 |
+
{
|
15 |
+
"name": "Quality_Factor_Researcher",
|
16 |
+
"profile": "As a quality factor researcher, the individual should evaluate companies based on financial health and earnings quality, utilize Python for quantitative analysis, and engage in team discussions to integrate quality assessments into investment decisions."
|
17 |
+
},
|
18 |
+
{
|
19 |
+
"name": "Volatility_Factor_Researcher",
|
20 |
+
"profile": "As a volatility factor researcher, one must analyze price fluctuations and risk metrics, demonstrate strong Python skills for risk modeling, and contribute to team efforts in developing risk-adjusted trading strategies."
|
21 |
+
},
|
22 |
+
{
|
23 |
+
"name": "Liquidity_Factor_Researcher",
|
24 |
+
"profile": "As a liquidity factor researcher, the position requires the ability to assess asset tradeability and market depth, use Python for liquidity analysis, and collaborate with the team to incorporate liquidity insights into trading algorithms."
|
25 |
+
},
|
26 |
+
{
|
27 |
+
"name": "Sentiment_Factor_Researcher",
|
28 |
+
"profile": "As a sentiment factor researcher, the individual should analyze market sentiment and investor opinions, be adept in Python for processing and analyzing large sentiment data sets, and work with colleagues to factor sentiment analysis into market predictions."
|
29 |
+
},
|
30 |
+
{
|
31 |
+
"name": "Macro_Factor_Researcher",
|
32 |
+
"profile": "As a macro factor researcher, one needs to understand the impact of macroeconomic indicators on markets, have strong Python skills for econometric analysis, and engage collaboratively in aligning investment strategies with macroeconomic conditions."
|
33 |
+
},
|
34 |
+
{
|
35 |
+
"name": "Portfolio_Manager",
|
36 |
+
"profile": "As a portfolio manager, the individual must integrate findings from various factor analyses to create and manage comprehensive investment strategies, demonstrate proficiency in Python for strategy development, and work collaboratively to ensure that these strategies meet the firm\u2019s investment goals and risk tolerance."
|
37 |
+
},
|
38 |
+
{
|
39 |
+
"name": "Quantitative_Analyst",
|
40 |
+
"profile": "As a quantitative analyst, one is responsible for validating investment strategies and factors, conducting back-tests and risk assessments using Python, and collaborating with the team to ensure that the investment approach is both statistically sound and aligned with risk management protocols."
|
41 |
+
},
|
42 |
+
{
|
43 |
+
"name": "Data_Specialist",
|
44 |
+
"profile": "As a data specialist, the individual must manage the acquisition, integrity, and delivery of financial and market data used by the research team, demonstrate proficiency in data processing and analysis tools, and collaborate effectively to support the quantitative analyses and investment strategies developed by the team. Always save processed data to disk and report its filename."
|
45 |
+
}
|
46 |
+
]
|
finrobot/__init__.py
ADDED
File without changes
|
finrobot/agents/__init__.py
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
|
finrobot/agents/agent_library.py
ADDED
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from finrobot.data_source import *
|
2 |
+
from finrobot.functional import *
|
3 |
+
from textwrap import dedent
|
4 |
+
|
5 |
+
library = [
|
6 |
+
{
|
7 |
+
"name": "Software_Developer",
|
8 |
+
"profile": "As a Software Developer for this position, you must be able to work collaboratively in a group chat environment to complete tasks assigned by a leader or colleague, primarily using Python programming expertise, excluding the need for code interpretation skills.",
|
9 |
+
},
|
10 |
+
{
|
11 |
+
"name": "Data_Analyst",
|
12 |
+
"profile": "As a Data Analyst for this position, you must be adept at analyzing data using Python, completing tasks assigned by leaders or colleagues, and collaboratively solving problems in a group chat setting with professionals of various roles. Reply 'TERMINATE' when everything is done.",
|
13 |
+
},
|
14 |
+
{
|
15 |
+
"name": "Programmer",
|
16 |
+
"profile": "As a Programmer for this position, you should be proficient in Python, able to effectively collaborate and solve problems within a group chat environment, and complete tasks assigned by leaders or colleagues without requiring expertise in code interpretation.",
|
17 |
+
},
|
18 |
+
{
|
19 |
+
"name": "Accountant",
|
20 |
+
"profile": "As an accountant in this position, one should possess a strong proficiency in accounting principles, the ability to effectively collaborate within team environments, such as group chats, to solve tasks, and have a basic understanding of Python for limited coding tasks, all while being able to follow directives from leaders and colleagues.",
|
21 |
+
},
|
22 |
+
{
|
23 |
+
"name": "Statistician",
|
24 |
+
"profile": "As a Statistician, the applicant should possess a strong background in statistics or mathematics, proficiency in Python for data analysis, the ability to work collaboratively in a team setting through group chats, and readiness to tackle and solve tasks delegated by supervisors or peers.",
|
25 |
+
},
|
26 |
+
{
|
27 |
+
"name": "IT_Specialist",
|
28 |
+
"profile": "As an IT Specialist, you should possess strong problem-solving skills, be able to effectively collaborate within a team setting through group chats, complete tasks assigned by leaders or colleagues, and have proficiency in Python programming, excluding the need for code interpretation expertise.",
|
29 |
+
},
|
30 |
+
{
|
31 |
+
"name": "Artificial_Intelligence_Engineer",
|
32 |
+
"profile": "As an Artificial Intelligence Engineer, you should be adept in Python, able to fulfill tasks assigned by leaders or colleagues, and capable of collaboratively solving problems in a group chat with diverse professionals.",
|
33 |
+
},
|
34 |
+
{
|
35 |
+
"name": "Financial_Analyst",
|
36 |
+
"profile": "As a Financial Analyst, one must possess strong analytical and problem-solving abilities, be proficient in Python for data analysis, have excellent communication skills to collaborate effectively in group chats, and be capable of completing assignments delegated by leaders or colleagues.",
|
37 |
+
},
|
38 |
+
{
|
39 |
+
"name": "Market_Analyst",
|
40 |
+
"profile": "As a Market Analyst, one must possess strong analytical and problem-solving abilities, collect necessary financial information and aggregate them based on client's requirement. For coding tasks, only use the functions you have been provided with. Reply TERMINATE when the task is done.",
|
41 |
+
"toolkits": [
|
42 |
+
{
|
43 |
+
"function": FinnHubUtils.get_company_profile,
|
44 |
+
"name": "get_company_profile",
|
45 |
+
"description": "get a company's profile information",
|
46 |
+
},
|
47 |
+
{
|
48 |
+
"function": FinnHubUtils.get_company_news,
|
49 |
+
"name": "get_company_news",
|
50 |
+
"description": "retrieve market news related to designated company",
|
51 |
+
},
|
52 |
+
{
|
53 |
+
"function": FinnHubUtils.get_basic_financials,
|
54 |
+
"name": "get_financial_basics",
|
55 |
+
"description": "get latest financial basics for a designated company",
|
56 |
+
},
|
57 |
+
{
|
58 |
+
"function": YFinanceUtils.get_stock_data,
|
59 |
+
"name": "get_stock_data",
|
60 |
+
"description": "retrieve stock price data for designated ticker symbol",
|
61 |
+
},
|
62 |
+
],
|
63 |
+
},
|
64 |
+
{
|
65 |
+
"name": "Expert_Investor",
|
66 |
+
"profile": dedent(
|
67 |
+
f"""
|
68 |
+
Role: Expert Investor
|
69 |
+
Department: Finance
|
70 |
+
Primary Responsibility: Generation of Customized Financial Analysis Reports
|
71 |
+
|
72 |
+
Role Description:
|
73 |
+
As an Expert Investor within the finance domain, your expertise is harnessed to develop bespoke Financial Analysis Reports that cater to specific client requirements. This role demands a deep dive into financial statements and market data to unearth insights regarding a company's financial performance and stability. Engaging directly with clients to gather essential information and continuously refining the report with their feedback ensures the final product precisely meets their needs and expectations.
|
74 |
+
|
75 |
+
Key Objectives:
|
76 |
+
|
77 |
+
Analytical Precision: Employ meticulous analytical prowess to interpret financial data, identifying underlying trends and anomalies.
|
78 |
+
Effective Communication: Simplify and effectively convey complex financial narratives, making them accessible and actionable to non-specialist audiences.
|
79 |
+
Client Focus: Dynamically tailor reports in response to client feedback, ensuring the final analysis aligns with their strategic objectives.
|
80 |
+
Adherence to Excellence: Maintain the highest standards of quality and integrity in report generation, following established benchmarks for analytical rigor.
|
81 |
+
Performance Indicators:
|
82 |
+
The efficacy of the Financial Analysis Report is measured by its utility in providing clear, actionable insights. This encompasses aiding corporate decision-making, pinpointing areas for operational enhancement, and offering a lucid evaluation of the company's financial health. Success is ultimately reflected in the report's contribution to informed investment decisions and strategic planning.
|
83 |
+
|
84 |
+
Reply TERMINATE when everything is settled.
|
85 |
+
"""
|
86 |
+
),
|
87 |
+
"toolkits": [
|
88 |
+
FMPUtils.get_sec_report, # Retrieve SEC report url and filing date
|
89 |
+
IPythonUtils.display_image, # Display image in IPython
|
90 |
+
TextUtils.check_text_length, # Check text length
|
91 |
+
ReportLabUtils.build_annual_report, # Build annual report in designed pdf format
|
92 |
+
ReportAnalysisUtils, # Expert Knowledge for Report Analysis
|
93 |
+
ReportChartUtils, # Expert Knowledge for Report Chart Plotting
|
94 |
+
],
|
95 |
+
},
|
96 |
+
]
|
97 |
+
library = {d["name"]: d for d in library}
|
finrobot/agents/utils.py
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
def order_trigger(sender):
|
2 |
+
# Check if the last message contains the path to the instruction text file
|
3 |
+
return "instruction & resources saved to" in sender.last_message()["content"]
|
4 |
+
|
5 |
+
|
6 |
+
def order_message(recipient, messages, sender, config):
|
7 |
+
# Extract the path to the instruction text file from the last message
|
8 |
+
full_order = recipient.chat_messages_for_summary(sender)[-1]["content"]
|
9 |
+
txt_path = full_order.replace("instruction & resources saved to ", "").strip()
|
10 |
+
with open(txt_path, "r") as f:
|
11 |
+
instruction = f.read() + "\n\nReply TERMINATE at the end of your response."
|
12 |
+
return instruction
|
finrobot/agents/workflow.py
ADDED
@@ -0,0 +1,175 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from .agent_library import library
|
2 |
+
from typing import Any, Callable, Dict, List, Literal
|
3 |
+
import autogen
|
4 |
+
from autogen.cache import Cache
|
5 |
+
from autogen.agentchat.contrib.retrieve_user_proxy_agent import RetrieveUserProxyAgent
|
6 |
+
|
7 |
+
from ..toolkits import register_toolkits
|
8 |
+
from .utils import *
|
9 |
+
|
10 |
+
|
11 |
+
class FinRobot(autogen.AssistantAgent):
|
12 |
+
|
13 |
+
def __init__(
|
14 |
+
self,
|
15 |
+
name: str,
|
16 |
+
system_message: str | None = None,
|
17 |
+
toolkits: List[Callable | dict | type] = [],
|
18 |
+
proxy: autogen.UserProxyAgent | None = None,
|
19 |
+
**kwargs,
|
20 |
+
):
|
21 |
+
orig_name = name
|
22 |
+
name = name.replace("_Shadow", "")
|
23 |
+
assert name in library, f"FinRobot {name} not found in agent library."
|
24 |
+
|
25 |
+
default_toolkits = library[name].get("toolkits", [])
|
26 |
+
default_system_message = library[name].get("profile", "")
|
27 |
+
|
28 |
+
self.toolkits = toolkits or default_toolkits
|
29 |
+
system_message = system_message or default_system_message
|
30 |
+
|
31 |
+
assert bool(system_message), f"System message is required for {name}."
|
32 |
+
|
33 |
+
super().__init__(orig_name, system_message, **kwargs)
|
34 |
+
if proxy is not None:
|
35 |
+
register_toolkits(self.toolkits, self, proxy)
|
36 |
+
|
37 |
+
|
38 |
+
from autogen.agentchat.contrib.retrieve_assistant_agent import RetrieveAssistantAgent
|
39 |
+
|
40 |
+
|
41 |
+
class SingleAssistant:
|
42 |
+
|
43 |
+
def __init__(
|
44 |
+
self,
|
45 |
+
name: str,
|
46 |
+
llm_config: Dict[str, Any] = {},
|
47 |
+
is_termination_msg=lambda x: x.get("content", "")
|
48 |
+
and x.get("content", "").endswith("TERMINATE"),
|
49 |
+
human_input_mode="NEVER",
|
50 |
+
max_consecutive_auto_reply=10,
|
51 |
+
code_execution_config={
|
52 |
+
"work_dir": "coding",
|
53 |
+
"use_docker": False,
|
54 |
+
},
|
55 |
+
**kwargs,
|
56 |
+
):
|
57 |
+
|
58 |
+
self.user_proxy = autogen.UserProxyAgent(
|
59 |
+
name="User_Proxy",
|
60 |
+
is_termination_msg=is_termination_msg,
|
61 |
+
human_input_mode=human_input_mode,
|
62 |
+
max_consecutive_auto_reply=max_consecutive_auto_reply,
|
63 |
+
code_execution_config=code_execution_config,
|
64 |
+
**kwargs,
|
65 |
+
)
|
66 |
+
self.assistant = FinRobot(
|
67 |
+
name,
|
68 |
+
llm_config=llm_config,
|
69 |
+
proxy=self.user_proxy,
|
70 |
+
)
|
71 |
+
|
72 |
+
def chat(self, message: str, use_cache=False, **kwargs):
|
73 |
+
with Cache.disk() as cache:
|
74 |
+
self.user_proxy.initiate_chat(
|
75 |
+
self.assistant,
|
76 |
+
message=message,
|
77 |
+
cache=cache if use_cache else None,
|
78 |
+
**kwargs,
|
79 |
+
)
|
80 |
+
|
81 |
+
|
82 |
+
class SingleAssistantRAG:
|
83 |
+
|
84 |
+
def __init__(
|
85 |
+
self,
|
86 |
+
name: str,
|
87 |
+
llm_config: Dict[str, Any] = {},
|
88 |
+
is_termination_msg=lambda x: x.get("content", "")
|
89 |
+
and x.get("content", "").endswith("TERMINATE"),
|
90 |
+
human_input_mode="NEVER",
|
91 |
+
max_consecutive_auto_reply=10,
|
92 |
+
code_execution_config={
|
93 |
+
"work_dir": "coding",
|
94 |
+
"use_docker": False,
|
95 |
+
},
|
96 |
+
retrieve_config=None,
|
97 |
+
**kwargs,
|
98 |
+
):
|
99 |
+
self.user_proxy = RetrieveUserProxyAgent(
|
100 |
+
name="User_Proxy",
|
101 |
+
is_termination_msg=is_termination_msg,
|
102 |
+
human_input_mode=human_input_mode,
|
103 |
+
max_consecutive_auto_reply=max_consecutive_auto_reply,
|
104 |
+
code_execution_config=code_execution_config,
|
105 |
+
retrieve_config=retrieve_config,
|
106 |
+
**kwargs,
|
107 |
+
)
|
108 |
+
self.assistant = FinRobot(
|
109 |
+
name,
|
110 |
+
llm_config=llm_config,
|
111 |
+
proxy=self.user_proxy,
|
112 |
+
)
|
113 |
+
|
114 |
+
def chat(self, message: str, use_cache=False, **kwargs):
|
115 |
+
with Cache.disk() as cache:
|
116 |
+
self.user_proxy.initiate_chat(
|
117 |
+
self.assistant,
|
118 |
+
message=self.user_proxy.message_generator,
|
119 |
+
problem=message,
|
120 |
+
cache=cache if use_cache else None,
|
121 |
+
**kwargs,
|
122 |
+
)
|
123 |
+
|
124 |
+
|
125 |
+
class SingleAssistantShadow(SingleAssistant):
|
126 |
+
|
127 |
+
def __init__(
|
128 |
+
self,
|
129 |
+
name: str,
|
130 |
+
llm_config: Dict[str, Any] = {},
|
131 |
+
is_termination_msg=lambda x: x.get("content", "")
|
132 |
+
and x.get("content", "").endswith("TERMINATE"),
|
133 |
+
human_input_mode="NEVER",
|
134 |
+
max_consecutive_auto_reply=10,
|
135 |
+
code_execution_config={
|
136 |
+
"work_dir": "coding",
|
137 |
+
"use_docker": False,
|
138 |
+
},
|
139 |
+
**kwargs,
|
140 |
+
):
|
141 |
+
super().__init__(
|
142 |
+
name,
|
143 |
+
llm_config=llm_config,
|
144 |
+
is_termination_msg=is_termination_msg,
|
145 |
+
human_input_mode=human_input_mode,
|
146 |
+
max_consecutive_auto_reply=max_consecutive_auto_reply,
|
147 |
+
code_execution_config=code_execution_config,
|
148 |
+
**kwargs,
|
149 |
+
)
|
150 |
+
self.assistant = FinRobot(
|
151 |
+
name,
|
152 |
+
llm_config=llm_config,
|
153 |
+
is_termination_msg=lambda x: x.get("content", "")
|
154 |
+
and x.get("content", "").endswith("TERMINATE"),
|
155 |
+
proxy=self.user_proxy,
|
156 |
+
)
|
157 |
+
self.assistant_shadow = FinRobot(
|
158 |
+
name + "_Shadow",
|
159 |
+
toolkits=[],
|
160 |
+
llm_config=llm_config,
|
161 |
+
proxy=None,
|
162 |
+
)
|
163 |
+
self.assistant.register_nested_chats(
|
164 |
+
[
|
165 |
+
{
|
166 |
+
"sender": self.assistant,
|
167 |
+
"recipient": self.assistant_shadow,
|
168 |
+
"message": order_message,
|
169 |
+
"summary_method": "last_msg",
|
170 |
+
"max_turns": 2,
|
171 |
+
"silent": True, # mute the chat summary
|
172 |
+
}
|
173 |
+
],
|
174 |
+
trigger=order_trigger,
|
175 |
+
)
|
finrobot/data_source/__init__.py
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import importlib.util
|
2 |
+
|
3 |
+
from .finnhub_utils import FinnHubUtils
|
4 |
+
from .yfinance_utils import YFinanceUtils
|
5 |
+
from .fmp_utils import FMPUtils
|
6 |
+
from .sec_utils import SECUtils
|
7 |
+
|
8 |
+
if importlib.util.find_spec("finnlp") is not None:
|
9 |
+
from .finnlp_utils import FinNLPUtils
|
10 |
+
|
11 |
+
__all__ = ["FinNLPUtils", "FinnHubUtils", "YFinanceUtils", "FMPUtils", "SECUtils"]
|
12 |
+
else:
|
13 |
+
__all__ = ["FinnHubUtils", "YFinanceUtils", "FMPUtils", "SECUtils"]
|
finrobot/data_source/finnhub_utils.py
ADDED
@@ -0,0 +1,161 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import finnhub
|
3 |
+
import pandas as pd
|
4 |
+
import json
|
5 |
+
import random
|
6 |
+
from typing import Annotated
|
7 |
+
from collections import defaultdict
|
8 |
+
from functools import wraps
|
9 |
+
from datetime import datetime
|
10 |
+
from ..utils import decorate_all_methods, save_output, SavePathType
|
11 |
+
|
12 |
+
|
13 |
+
def init_finnhub_client(func):
|
14 |
+
@wraps(func)
|
15 |
+
def wrapper(*args, **kwargs):
|
16 |
+
global finnhub_client
|
17 |
+
if os.environ.get("FINNHUB_API_KEY") is None:
|
18 |
+
print(
|
19 |
+
"Please set the environment variable FINNHUB_API_KEY to use the Finnhub API."
|
20 |
+
)
|
21 |
+
return None
|
22 |
+
else:
|
23 |
+
finnhub_client = finnhub.Client(api_key=os.environ["FINNHUB_API_KEY"])
|
24 |
+
print("Finnhub client initialized")
|
25 |
+
return func(*args, **kwargs)
|
26 |
+
|
27 |
+
# wrapper.__annotations__ = func.__annotations__
|
28 |
+
return wrapper
|
29 |
+
|
30 |
+
|
31 |
+
@decorate_all_methods(init_finnhub_client)
|
32 |
+
class FinnHubUtils:
|
33 |
+
|
34 |
+
def get_company_profile(symbol: Annotated[str, "ticker symbol"]) -> str:
|
35 |
+
|
36 |
+
profile = finnhub_client.company_profile2(symbol=symbol)
|
37 |
+
if not profile:
|
38 |
+
return f"Failed to find company profile for symbol {symbol} from finnhub!"
|
39 |
+
|
40 |
+
formatted_str = (
|
41 |
+
"[Company Introduction]:\n\n{name} is a leading entity in the {finnhubIndustry} sector. "
|
42 |
+
"Incorporated and publicly traded since {ipo}, the company has established its reputation as "
|
43 |
+
"one of the key players in the market. As of today, {name} has a market capitalization "
|
44 |
+
"of {marketCapitalization:.2f} in {currency}, with {shareOutstanding:.2f} shares outstanding."
|
45 |
+
"\n\n{name} operates primarily in the {country}, trading under the ticker {ticker} on the {exchange}. "
|
46 |
+
"As a dominant force in the {finnhubIndustry} space, the company continues to innovate and drive "
|
47 |
+
"progress within the industry."
|
48 |
+
).format(**profile)
|
49 |
+
|
50 |
+
return formatted_str
|
51 |
+
|
52 |
+
def get_company_news(
|
53 |
+
symbol: Annotated[str, "ticker symbol"],
|
54 |
+
start_date: Annotated[
|
55 |
+
str,
|
56 |
+
"start date of the search period for the company's basic financials, yyyy-mm-dd",
|
57 |
+
],
|
58 |
+
end_date: Annotated[
|
59 |
+
str,
|
60 |
+
"end date of the search period for the company's basic financials, yyyy-mm-dd",
|
61 |
+
],
|
62 |
+
max_news_num: Annotated[
|
63 |
+
int, "maximum number of news to return, default to 10"
|
64 |
+
] = 10,
|
65 |
+
save_path: SavePathType = None,
|
66 |
+
) -> pd.DataFrame:
|
67 |
+
|
68 |
+
news = finnhub_client.company_news(symbol, _from=start_date, to=end_date)
|
69 |
+
if len(news) == 0:
|
70 |
+
print(f"No company news found for symbol {symbol} from finnhub!")
|
71 |
+
news = [
|
72 |
+
{
|
73 |
+
"date": datetime.fromtimestamp(n["datetime"]).strftime("%Y%m%d%H%M%S"),
|
74 |
+
"headline": n["headline"],
|
75 |
+
"summary": n["summary"],
|
76 |
+
}
|
77 |
+
for n in news
|
78 |
+
]
|
79 |
+
# Randomly select a subset of news if the number of news exceeds the maximum
|
80 |
+
if len(news) > max_news_num:
|
81 |
+
news = random.choices(news, k=max_news_num)
|
82 |
+
news.sort(key=lambda x: x["date"])
|
83 |
+
output = pd.DataFrame(news)
|
84 |
+
save_output(output, f"company news of {symbol}", save_path=save_path)
|
85 |
+
|
86 |
+
return output
|
87 |
+
|
88 |
+
def get_basic_financials_history(
|
89 |
+
symbol: Annotated[str, "ticker symbol"],
|
90 |
+
freq: Annotated[
|
91 |
+
str,
|
92 |
+
"reporting frequency of the company's basic financials: annual / quarterly",
|
93 |
+
],
|
94 |
+
start_date: Annotated[
|
95 |
+
str,
|
96 |
+
"start date of the search period for the company's basic financials, yyyy-mm-dd",
|
97 |
+
],
|
98 |
+
end_date: Annotated[
|
99 |
+
str,
|
100 |
+
"end date of the search period for the company's basic financials, yyyy-mm-dd",
|
101 |
+
],
|
102 |
+
selected_columns: Annotated[
|
103 |
+
list[str] | None,
|
104 |
+
"List of column names of news to return, should be chosen from 'assetTurnoverTTM', 'bookValue', 'cashRatio', 'currentRatio', 'ebitPerShare', 'eps', 'ev', 'fcfMargin', 'fcfPerShareTTM', 'grossMargin', 'inventoryTurnoverTTM', 'longtermDebtTotalAsset', 'longtermDebtTotalCapital', 'longtermDebtTotalEquity', 'netDebtToTotalCapital', 'netDebtToTotalEquity', 'netMargin', 'operatingMargin', 'payoutRatioTTM', 'pb', 'peTTM', 'pfcfTTM', 'pretaxMargin', 'psTTM', 'ptbv', 'quickRatio', 'receivablesTurnoverTTM', 'roaTTM', 'roeTTM', 'roicTTM', 'rotcTTM', 'salesPerShare', 'sgaToSale', 'tangibleBookValue', 'totalDebtToEquity', 'totalDebtToTotalAsset', 'totalDebtToTotalCapital', 'totalRatio'",
|
105 |
+
] = None,
|
106 |
+
save_path: SavePathType = None,
|
107 |
+
) -> pd.DataFrame:
|
108 |
+
|
109 |
+
if freq not in ["annual", "quarterly"]:
|
110 |
+
return f"Invalid reporting frequency {freq}. Please specify either 'annual' or 'quarterly'."
|
111 |
+
|
112 |
+
basic_financials = finnhub_client.company_basic_financials(symbol, "all")
|
113 |
+
if not basic_financials["series"]:
|
114 |
+
return f"Failed to find basic financials for symbol {symbol} from finnhub! Try a different symbol."
|
115 |
+
|
116 |
+
output_dict = defaultdict(dict)
|
117 |
+
for metric, value_list in basic_financials["series"][freq].items():
|
118 |
+
if selected_columns and metric not in selected_columns:
|
119 |
+
continue
|
120 |
+
for value in value_list:
|
121 |
+
if value["period"] >= start_date and value["period"] <= end_date:
|
122 |
+
output_dict[metric].update({value["period"]: value["v"]})
|
123 |
+
|
124 |
+
financials_output = pd.DataFrame(output_dict)
|
125 |
+
financials_output = financials_output.rename_axis(index="date")
|
126 |
+
save_output(financials_output, "basic financials", save_path=save_path)
|
127 |
+
|
128 |
+
return financials_output
|
129 |
+
|
130 |
+
def get_basic_financials(
|
131 |
+
symbol: Annotated[str, "ticker symbol"],
|
132 |
+
selected_columns: Annotated[
|
133 |
+
list[str] | None,
|
134 |
+
"List of column names of news to return, should be chosen from 'assetTurnoverTTM', 'bookValue', 'cashRatio', 'currentRatio', 'ebitPerShare', 'eps', 'ev', 'fcfMargin', 'fcfPerShareTTM', 'grossMargin', 'inventoryTurnoverTTM', 'longtermDebtTotalAsset', 'longtermDebtTotalCapital', 'longtermDebtTotalEquity', 'netDebtToTotalCapital', 'netDebtToTotalEquity', 'netMargin', 'operatingMargin', 'payoutRatioTTM', 'pb', 'peTTM', 'pfcfTTM', 'pretaxMargin', 'psTTM', 'ptbv', 'quickRatio', 'receivablesTurnoverTTM', 'roaTTM', 'roeTTM', 'roicTTM', 'rotcTTM', 'salesPerShare', 'sgaToSale', 'tangibleBookValue', 'totalDebtToEquity', 'totalDebtToTotalAsset', 'totalDebtToTotalCapital', 'totalRatio','10DayAverageTradingVolume', '13WeekPriceReturnDaily', '26WeekPriceReturnDaily', '3MonthADReturnStd', '3MonthAverageTradingVolume', '52WeekHigh', '52WeekHighDate', '52WeekLow', '52WeekLowDate', '52WeekPriceReturnDaily', '5DayPriceReturnDaily', 'assetTurnoverAnnual', 'assetTurnoverTTM', 'beta', 'bookValuePerShareAnnual', 'bookValuePerShareQuarterly', 'bookValueShareGrowth5Y', 'capexCagr5Y', 'cashFlowPerShareAnnual', 'cashFlowPerShareQuarterly', 'cashFlowPerShareTTM', 'cashPerSharePerShareAnnual', 'cashPerSharePerShareQuarterly', 'currentDividendYieldTTM', 'currentEv/freeCashFlowAnnual', 'currentEv/freeCashFlowTTM', 'currentRatioAnnual', 'currentRatioQuarterly', 'dividendGrowthRate5Y', 'dividendPerShareAnnual', 'dividendPerShareTTM', 'dividendYieldIndicatedAnnual', 'ebitdPerShareAnnual', 'ebitdPerShareTTM', 'ebitdaCagr5Y', 'ebitdaInterimCagr5Y', 'enterpriseValue', 'epsAnnual', 'epsBasicExclExtraItemsAnnual', 'epsBasicExclExtraItemsTTM', 'epsExclExtraItemsAnnual', 'epsExclExtraItemsTTM', 'epsGrowth3Y', 'epsGrowth5Y', 'epsGrowthQuarterlyYoy', 'epsGrowthTTMYoy', 'epsInclExtraItemsAnnual', 'epsInclExtraItemsTTM', 'epsNormalizedAnnual', 'epsTTM', 'focfCagr5Y', 'grossMargin5Y', 'grossMarginAnnual', 'grossMarginTTM', 'inventoryTurnoverAnnual', 'inventoryTurnoverTTM', 'longTermDebt/equityAnnual', 'longTermDebt/equityQuarterly', 'marketCapitalization', 'monthToDatePriceReturnDaily', 'netIncomeEmployeeAnnual', 'netIncomeEmployeeTTM', 'netInterestCoverageAnnual', 'netInterestCoverageTTM', 'netMarginGrowth5Y', 'netProfitMargin5Y', 'netProfitMarginAnnual', 'netProfitMarginTTM', 'operatingMargin5Y', 'operatingMarginAnnual', 'operatingMarginTTM', 'payoutRatioAnnual', 'payoutRatioTTM', 'pbAnnual', 'pbQuarterly', 'pcfShareAnnual', 'pcfShareTTM', 'peAnnual', 'peBasicExclExtraTTM', 'peExclExtraAnnual', 'peExclExtraTTM', 'peInclExtraTTM', 'peNormalizedAnnual', 'peTTM', 'pfcfShareAnnual', 'pfcfShareTTM', 'pretaxMargin5Y', 'pretaxMarginAnnual', 'pretaxMarginTTM', 'priceRelativeToS&P50013Week', 'priceRelativeToS&P50026Week', 'priceRelativeToS&P5004Week', 'priceRelativeToS&P50052Week', 'priceRelativeToS&P500Ytd', 'psAnnual', 'psTTM', 'ptbvAnnual', 'ptbvQuarterly', 'quickRatioAnnual', 'quickRatioQuarterly', 'receivablesTurnoverAnnual', 'receivablesTurnoverTTM', 'revenueEmployeeAnnual', 'revenueEmployeeTTM', 'revenueGrowth3Y', 'revenueGrowth5Y', 'revenueGrowthQuarterlyYoy', 'revenueGrowthTTMYoy', 'revenuePerShareAnnual', 'revenuePerShareTTM', 'revenueShareGrowth5Y', 'roa5Y', 'roaRfy', 'roaTTM', 'roe5Y', 'roeRfy', 'roeTTM', 'roi5Y', 'roiAnnual', 'roiTTM', 'tangibleBookValuePerShareAnnual', 'tangibleBookValuePerShareQuarterly', 'tbvCagr5Y', 'totalDebt/totalEquityAnnual', 'totalDebt/totalEquityQuarterly', 'yearToDatePriceReturnDaily'",
|
135 |
+
] = None,
|
136 |
+
) -> str:
|
137 |
+
|
138 |
+
basic_financials = finnhub_client.company_basic_financials(symbol, "all")
|
139 |
+
if not basic_financials["series"]:
|
140 |
+
return f"Failed to find basic financials for symbol {symbol} from finnhub! Try a different symbol."
|
141 |
+
|
142 |
+
output_dict = basic_financials["metric"]
|
143 |
+
for metric, value_list in basic_financials["series"]["quarterly"].items():
|
144 |
+
value = value_list[0]
|
145 |
+
output_dict.update({metric: value["v"]})
|
146 |
+
|
147 |
+
for k in output_dict.keys():
|
148 |
+
if selected_columns and k not in selected_columns:
|
149 |
+
output_dict.pop(k)
|
150 |
+
|
151 |
+
return json.dumps(output_dict, indent=2)
|
152 |
+
|
153 |
+
|
154 |
+
if __name__ == "__main__":
|
155 |
+
|
156 |
+
from finrobot.utils import register_keys_from_json
|
157 |
+
|
158 |
+
register_keys_from_json("../../config_api_keys")
|
159 |
+
# print(FinnHubUtils.get_company_profile("AAPL"))
|
160 |
+
# print(FinnHubUtils.get_basic_financials_history("AAPL", "annual", "2019-01-01", "2021-01-01"))
|
161 |
+
print(FinnHubUtils.get_basic_financials("AAPL"))
|
finrobot/data_source/finnlp_utils.py
ADDED
@@ -0,0 +1,234 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from typing import Annotated
|
3 |
+
from pandas import DataFrame
|
4 |
+
|
5 |
+
from finnlp.data_sources.news.cnbc_streaming import CNBC_Streaming
|
6 |
+
from finnlp.data_sources.news.yicai_streaming import Yicai_Streaming
|
7 |
+
from finnlp.data_sources.news.investorplace_streaming import InvestorPlace_Streaming
|
8 |
+
# from finnlp.data_sources.news.eastmoney_streaming import Eastmoney_Streaming
|
9 |
+
|
10 |
+
from finnlp.data_sources.social_media.xueqiu_streaming import Xueqiu_Streaming
|
11 |
+
from finnlp.data_sources.social_media.stocktwits_streaming import Stocktwits_Streaming
|
12 |
+
# from finnlp.data_sources.social_media.reddit_streaming import Reddit_Streaming
|
13 |
+
|
14 |
+
from finnlp.data_sources.news.sina_finance_date_range import Sina_Finance_Date_Range
|
15 |
+
from finnlp.data_sources.news.finnhub_date_range import Finnhub_Date_Range
|
16 |
+
|
17 |
+
from ..utils import save_output, SavePathType
|
18 |
+
|
19 |
+
|
20 |
+
US_Proxy = {
|
21 |
+
"use_proxy": "us_free",
|
22 |
+
"max_retry": 5,
|
23 |
+
"proxy_pages": 5,
|
24 |
+
}
|
25 |
+
CN_Proxy = {
|
26 |
+
"use_proxy": "china_free",
|
27 |
+
"max_retry": 5,
|
28 |
+
"proxy_pages": 5,
|
29 |
+
}
|
30 |
+
|
31 |
+
|
32 |
+
def streaming_download(streaming, config, tag, keyword, rounds, selected_columns, save_path):
|
33 |
+
downloader = streaming(config)
|
34 |
+
if hasattr(downloader, 'download_streaming_search'):
|
35 |
+
downloader.download_streaming_search(keyword, rounds)
|
36 |
+
elif hasattr(downloader, 'download_streaming_stock'):
|
37 |
+
downloader.download_streaming_stock(keyword, rounds)
|
38 |
+
else:
|
39 |
+
downloader.download_streaming_all(rounds)
|
40 |
+
# print(downloader.dataframe.columns)
|
41 |
+
selected = downloader.dataframe[selected_columns]
|
42 |
+
save_output(selected, tag, save_path)
|
43 |
+
return selected
|
44 |
+
|
45 |
+
|
46 |
+
def date_range_download(date_range, config, tag, start_date, end_date, stock, selected_columns, save_path):
|
47 |
+
downloader = date_range(config)
|
48 |
+
if hasattr(downloader, 'download_date_range_stock'):
|
49 |
+
downloader.download_date_range_stock(start_date, end_date, stock)
|
50 |
+
else:
|
51 |
+
downloader.download_date_range_all(start_date, end_date)
|
52 |
+
if hasattr(downloader, 'gather_content'):
|
53 |
+
downloader.gather_content()
|
54 |
+
# print(downloader.dataframe.columns)
|
55 |
+
selected_news = downloader.dataframe[selected_columns]
|
56 |
+
save_output(selected_news, tag, save_path)
|
57 |
+
return selected_news
|
58 |
+
|
59 |
+
|
60 |
+
class FinNLPUtils:
|
61 |
+
|
62 |
+
"""
|
63 |
+
Streaming News Download
|
64 |
+
"""
|
65 |
+
|
66 |
+
def cnbc_news_download(
|
67 |
+
keyword: Annotated[str, "Keyword to search in news stream"],
|
68 |
+
rounds: Annotated[int, "Number of rounds to search. Default to 1"] = 1,
|
69 |
+
selected_columns: Annotated[list[str], "List of column names of news to return, should be chosen from 'description', 'cn:lastPubDate', 'dateModified', 'cn:dateline', 'cn:branding', 'section', 'cn:type', 'author', 'cn:source', 'cn:subtype', 'duration', 'summary', 'expires', 'cn:sectionSubType', 'cn:contentClassification', 'pubdateunix', 'url', 'datePublished', 'cn:promoImage', 'cn:title', 'cn:keyword', 'cn:liveURL', 'brand', 'hint', 'hint_detail'. Default to ['author', 'datePublished', 'description' ,'section', 'cn:title', 'summary']"] = ["author", "datePublished", "description" ,"section", "cn:title", "summary"],
|
70 |
+
save_path: SavePathType = None
|
71 |
+
) -> DataFrame:
|
72 |
+
return streaming_download(CNBC_Streaming, {}, "CNBC News", keyword, rounds, selected_columns, save_path)
|
73 |
+
|
74 |
+
|
75 |
+
def yicai_news_download(
|
76 |
+
keyword: Annotated[str, "Keyword to search in news stream"],
|
77 |
+
rounds: Annotated[int, "Number of rounds to search. Default to 1"] = 1,
|
78 |
+
selected_columns: Annotated[list[str], "List of column names of news to return, should be chosen from 'author','channelid','creationDate','desc','id','previewImage','source','tags','title','topics','typeo','url','weight'. Default to ['author', 'creationDate', 'desc' ,'source', 'title']"] = ["author", "creationDate", "desc" ,"source", "title"],
|
79 |
+
save_path: SavePathType = None
|
80 |
+
) -> DataFrame:
|
81 |
+
return streaming_download(Yicai_Streaming, {}, "Yicai News", keyword, rounds, selected_columns, save_path)
|
82 |
+
|
83 |
+
|
84 |
+
def investor_place_news_download(
|
85 |
+
keyword: Annotated[str, "Keyword to search in news stream"],
|
86 |
+
rounds: Annotated[int, "Number of rounds to search. Default to 1"] = 1,
|
87 |
+
selected_columns: Annotated[list[str], "List of column names of news to return, should be chosen from 'title', 'time', 'author', 'summary'. Default to ['title', 'time', 'author', 'summary']"] = ['title', 'time', 'author', 'summary'],
|
88 |
+
save_path: SavePathType = None
|
89 |
+
) -> DataFrame:
|
90 |
+
return streaming_download(InvestorPlace_Streaming, {}, "Investor Place News", keyword, rounds, selected_columns, save_path)
|
91 |
+
|
92 |
+
|
93 |
+
# def eastmoney_news_download(
|
94 |
+
# stock: Annotated[str, "stock code, e.g. 600519"],
|
95 |
+
# pages: Annotated[int, "Number of pages to retrieve. Default to 1"] = 1,
|
96 |
+
# selected_columns: Annotated[list[str], "List of column names of news to return, should be chosen from 'title', 'time', 'author', 'summary'. Default to ['title', 'time', 'author', 'summary']"] = ['title', 'time', 'author', 'summary'],
|
97 |
+
# verbose: Annotated[bool, "Whether to print downloaded news to console. Default to True"] = True,
|
98 |
+
# save_path: Annotated[str, "If specified (recommended if the amount of news is large), the downloaded news will be saved to save_path, otherwise the news will be returned as a string. Default to None"] = None,
|
99 |
+
# ) -> str:
|
100 |
+
# return streaming_download(Eastmoney_Streaming, "Eastmoney", stock, pages, selected_columns, save_path)
|
101 |
+
|
102 |
+
|
103 |
+
"""
|
104 |
+
Date Range News Download
|
105 |
+
"""
|
106 |
+
|
107 |
+
def sina_finance_news_download(
|
108 |
+
start_date: Annotated[str, "Start date of the news to retrieve, YYYY-mm-dd"],
|
109 |
+
end_date: Annotated[str, "End date of the news to retrieve, YYYY-mm-dd"],
|
110 |
+
selected_columns: Annotated[list[str], """
|
111 |
+
List of column names of news to return, should be chosen from
|
112 |
+
'mediaid', 'productid', 'summary', 'ctime', 'url', 'author', 'stitle',
|
113 |
+
'authoruid', 'wapsummary', 'images', 'level', 'keywords', 'mlids',
|
114 |
+
'wapurl', 'columnid', 'oid', 'img', 'subjectid', 'commentid',
|
115 |
+
'ipad_vid', 'vid', 'video_id', 'channelid', 'intime',
|
116 |
+
'video_time_length', 'categoryid', 'hqChart', 'intro', 'is_cre_manual',
|
117 |
+
'icons', 'mtime', 'media_name', 'title', 'docid', 'urls', 'templateid',
|
118 |
+
'lids', 'wapurls', 'ext', 'comment_reply', 'comment_show', 'comment_total', 'praise',
|
119 |
+
'dispraise', 'important', 'content'. Default to ['title', 'author', 'content']
|
120 |
+
"""
|
121 |
+
] = ['title', 'author', 'content'],
|
122 |
+
save_path: SavePathType = None
|
123 |
+
) -> DataFrame:
|
124 |
+
return date_range_download(Sina_Finance_Date_Range, {}, "Sina Finance News", start_date, end_date, None, selected_columns, save_path)
|
125 |
+
|
126 |
+
|
127 |
+
def finnhub_news_download(
|
128 |
+
start_date: Annotated[str, "Start date of the news to retrieve, YYYY-mm-dd"],
|
129 |
+
end_date: Annotated[str, "End date of the news to retrieve, YYYY-mm-dd"],
|
130 |
+
stock: Annotated[str, "Stock symbol, e.g. AAPL"],
|
131 |
+
selected_columns: Annotated[list[str], "List of column names of news to return, should be chosen from 'category', 'datetime', 'headline', 'id', 'image', 'related', 'source', 'summary', 'url', 'content'. Default to ['headline', 'datetime', 'source', 'summary']"] = ['headline', 'datetime', 'source', 'summary'],
|
132 |
+
save_path: SavePathType = None
|
133 |
+
) -> DataFrame:
|
134 |
+
return date_range_download(Finnhub_Date_Range, {"token": os.environ['FINNHUB_API_KEY']}, "Finnhub News", start_date, end_date, stock, selected_columns, save_path)
|
135 |
+
|
136 |
+
|
137 |
+
"""
|
138 |
+
Social Media
|
139 |
+
"""
|
140 |
+
def xueqiu_social_media_download(
|
141 |
+
stock: Annotated[str, "Stock symbol, e.g. 'AAPL'"],
|
142 |
+
rounds: Annotated[int, "Number of rounds to search. Default to 1"] = 1,
|
143 |
+
selected_columns: Annotated[list[str], """
|
144 |
+
List of column names of news to return, should be chosen from blocked',
|
145 |
+
'blocking', 'canEdit', 'commentId', 'controversial',
|
146 |
+
'created_at', 'description', 'donate_count', 'donate_snowcoin',
|
147 |
+
'editable', 'expend', 'fav_count', 'favorited', 'flags', 'flagsObj',
|
148 |
+
'hot', 'id', 'is_answer', 'is_bonus', 'is_refused', 'is_reward',
|
149 |
+
'is_ss_multi_pic', 'legal_user_visible', 'like_count', 'liked', 'mark',
|
150 |
+
'pic', 'promotion_id', 'reply_count', 'retweet_count',
|
151 |
+
'retweet_status_id', 'reward_count', 'reward_user_count', 'rqid',
|
152 |
+
'source', 'source_feed', 'source_link', 'target', 'text', 'timeBefore',
|
153 |
+
'title', 'trackJson', 'truncated', 'truncated_by', 'type', 'user',
|
154 |
+
'user_id', 'view_count', 'firstImg', 'pic_sizes', 'edited_at'.
|
155 |
+
Default to ['created_at', 'description', 'title', 'text', 'target', 'source']
|
156 |
+
"""] = ['created_at', 'description', 'title', 'text', 'target', 'source'],
|
157 |
+
save_path: SavePathType = None
|
158 |
+
) -> DataFrame:
|
159 |
+
return streaming_download(Xueqiu_Streaming, {}, "Xueqiu Social Media", stock, rounds, selected_columns, save_path)
|
160 |
+
|
161 |
+
|
162 |
+
def stocktwits_social_media_download(
|
163 |
+
stock: Annotated[str, "Stock symbol, e.g. 'AAPL'"],
|
164 |
+
rounds: Annotated[int, "Number of rounds to search. Default to 1"] = 1,
|
165 |
+
selected_columns: Annotated[list[str], """
|
166 |
+
List of column names of news to return, should be chosen from 'id',
|
167 |
+
'body', 'created_at', 'user', 'source', 'symbols', 'prices',
|
168 |
+
'mentioned_users', 'entities', 'liked_by_self', 'reshared_by_self',
|
169 |
+
'conversation', 'links', 'likes', 'reshare_message', 'structurable',
|
170 |
+
'reshares'. Default to ['created_at', 'body']
|
171 |
+
"""] = ['created_at', 'body'],
|
172 |
+
save_path: SavePathType = None
|
173 |
+
) -> DataFrame:
|
174 |
+
return streaming_download(Stocktwits_Streaming, {}, "Stocktwits Social Media", stock, rounds, selected_columns, save_path)
|
175 |
+
|
176 |
+
|
177 |
+
# def reddit_social_media_download(
|
178 |
+
# pages: Annotated[int, "Number of pages to retrieve. Default to 1"] = 1,
|
179 |
+
# selected_columns: Annotated[list[str], """
|
180 |
+
# List of column names of news to return, should be chosen from 'id',
|
181 |
+
# 'body', 'created_at', 'user', 'source', 'symbols', 'prices',
|
182 |
+
# 'mentioned_users', 'entities', 'liked_by_self', 'reshared_by_self',
|
183 |
+
# 'conversation', 'links', 'likes', 'reshare_message', 'structurable',
|
184 |
+
# 'reshares'. Default to ['created_at', 'body']
|
185 |
+
# """] = ['created_at', 'body'],
|
186 |
+
# verbose: Annotated[bool, "Whether to print downloaded news to console. Default to True"] = True,
|
187 |
+
# save_path: Annotated[str, "If specified (recommended if the amount of news is large), the downloaded news will be saved to save_path. Default to None"] = None,
|
188 |
+
# ) -> DataFrame:
|
189 |
+
# return streaming_download(Reddit_Streaming, {}, "Reddit Social Media", None, pages, selected_columns, save_path)
|
190 |
+
|
191 |
+
|
192 |
+
"""
|
193 |
+
Company Announcements
|
194 |
+
(Not working well)
|
195 |
+
"""
|
196 |
+
|
197 |
+
# from finnlp.data_sources.company_announcement.sec import SEC_Announcement
|
198 |
+
# from finnlp.data_sources.company_announcement.juchao import Juchao_Announcement
|
199 |
+
|
200 |
+
|
201 |
+
# def sec_announcement_download(
|
202 |
+
# start_date: Annotated[str, "Start date of the news to retrieve, YYYY-mm-dd"],
|
203 |
+
# end_date: Annotated[str, "End date of the news to retrieve, YYYY-mm-dd"],
|
204 |
+
# stock: Annotated[str, "Stock symbol, e.g. AAPL"],
|
205 |
+
# selected_columns: Annotated[list[str], "List of column names of news to return, should be chosen from 'category', 'datetime', 'headline', 'id', 'image', 'related', 'source', 'summary', 'url', 'content'. Default to ['headline', 'datetime', 'source', 'summary']"] = ['headline', 'datetime', 'source', 'summary'],
|
206 |
+
# verbose: Annotated[bool, "Whether to print downloaded news to console. Default to True"] = True,
|
207 |
+
# save_path: Annotated[str, "If specified (recommended if the amount of news is large), the downloaded news will be saved to save_path. Default to None"] = None,
|
208 |
+
# ) -> DataFrame:
|
209 |
+
# return date_range_download(SEC_Announcement, {}, "SEC Announcements", start_date, end_date, stock, selected_columns, save_path)
|
210 |
+
|
211 |
+
|
212 |
+
# def juchao_announcement_download(
|
213 |
+
# start_date: Annotated[str, "Start date of the news to retrieve, YYYY-mm-dd"],
|
214 |
+
# end_date: Annotated[str, "End date of the news to retrieve, YYYY-mm-dd"],
|
215 |
+
# stock: Annotated[str, "Stock code, e.g. 000001"],
|
216 |
+
# selected_columns: Annotated[list[str], "List of column names of news to return, should be chosen from 'category', 'datetime', 'headline', 'id', 'image', 'related', 'source', 'summary', 'url', 'content'. Default to ['headline', 'datetime', 'source', 'summary']"] = ['headline', 'datetime', 'source', 'summary'],
|
217 |
+
# verbose: Annotated[bool, "Whether to print downloaded news to console. Default to True"] = True,
|
218 |
+
# save_path: Annotated[str, "If specified (recommended if the amount of news is large), the downloaded news will be saved to save_path. Default to None"] = None,
|
219 |
+
# ) -> DataFrame:
|
220 |
+
# return date_range_download(Juchao_Announcement, {}, "Juchao Announcements", start_date, end_date, stock, selected_columns, save_path)
|
221 |
+
|
222 |
+
|
223 |
+
if __name__ == "__main__":
|
224 |
+
|
225 |
+
print(FinNLPUtils.yicai_news_download("茅台", save_path="yicai_maotai.csv"))
|
226 |
+
# print(cnbc_news_download("tesla", save_path="cnbc_tesla.csv"))
|
227 |
+
# investor_place_news_download("tesla", save_path="invpl_tesla.csv")
|
228 |
+
# eastmoney_news_download("600519", save_path="estmny_maotai.csv")
|
229 |
+
# sina_finance_news_download("2024-03-02", "2024-03-02", save_path="sina_news.csv")
|
230 |
+
# finnhub_news_download("2024-03-02", "2024-03-02", "AAPL", save_path="finnhub_aapl_news.csv")
|
231 |
+
# stocktwits_social_media_download("AAPL", save_path="stocktwits_aapl.csv")
|
232 |
+
# xueqiu_social_media_download("茅台", save_path="xueqiu_maotai.csv")
|
233 |
+
# reddit_social_media_download(save_path="reddit_social_media.csv")
|
234 |
+
# juchao_announcement_download("000001", "2020-01-01", "2020-06-01", save_path="sec_announcement.csv")
|
finrobot/data_source/fmp_utils.py
ADDED
@@ -0,0 +1,200 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import requests
|
3 |
+
import numpy as np
|
4 |
+
import pandas as pd
|
5 |
+
from datetime import datetime, timedelta
|
6 |
+
from ..utils import decorate_all_methods, get_next_weekday
|
7 |
+
|
8 |
+
# from finrobot.utils import decorate_all_methods, get_next_weekday
|
9 |
+
from functools import wraps
|
10 |
+
from typing import Annotated
|
11 |
+
|
12 |
+
|
13 |
+
def init_fmp_api(func):
|
14 |
+
@wraps(func)
|
15 |
+
def wrapper(*args, **kwargs):
|
16 |
+
global fmp_api_key
|
17 |
+
if os.environ.get("FMP_API_KEY") is None:
|
18 |
+
print("Please set the environment variable FMP_API_KEY to use the FMP API.")
|
19 |
+
return None
|
20 |
+
else:
|
21 |
+
fmp_api_key = os.environ["FMP_API_KEY"]
|
22 |
+
print("FMP api key found successfully.")
|
23 |
+
return func(*args, **kwargs)
|
24 |
+
|
25 |
+
return wrapper
|
26 |
+
|
27 |
+
|
28 |
+
@decorate_all_methods(init_fmp_api)
|
29 |
+
class FMPUtils:
|
30 |
+
|
31 |
+
def get_target_price(
|
32 |
+
ticker_symbol: Annotated[str, "ticker symbol"],
|
33 |
+
date: Annotated[str, "date of the target price, should be 'yyyy-mm-dd'"],
|
34 |
+
) -> str:
|
35 |
+
"""Get the target price for a given stock on a given date"""
|
36 |
+
# API URL
|
37 |
+
url = f"https://financialmodelingprep.com/api/v4/price-target?symbol={ticker_symbol}&apikey={fmp_api_key}"
|
38 |
+
|
39 |
+
# 发送GET请求
|
40 |
+
price_target = "Not Given"
|
41 |
+
response = requests.get(url)
|
42 |
+
|
43 |
+
# 确保请求成功
|
44 |
+
if response.status_code == 200:
|
45 |
+
# 解析JSON数据
|
46 |
+
data = response.json()
|
47 |
+
est = []
|
48 |
+
|
49 |
+
date = datetime.strptime(date, "%Y-%m-%d")
|
50 |
+
for tprice in data:
|
51 |
+
tdate = tprice["publishedDate"].split("T")[0]
|
52 |
+
tdate = datetime.strptime(tdate, "%Y-%m-%d")
|
53 |
+
if abs((tdate - date).days) <= 1:
|
54 |
+
est.append(tprice["priceTarget"])
|
55 |
+
|
56 |
+
if est:
|
57 |
+
price_target = f"{np.min(est)} - {np.max(est)} (md. {np.median(est)})"
|
58 |
+
else:
|
59 |
+
price_target = "N/A"
|
60 |
+
else:
|
61 |
+
return f"Failed to retrieve data: {response.status_code}"
|
62 |
+
|
63 |
+
return price_target
|
64 |
+
|
65 |
+
def get_sec_report(
|
66 |
+
ticker_symbol: Annotated[str, "ticker symbol"],
|
67 |
+
fyear: Annotated[
|
68 |
+
str,
|
69 |
+
"year of the 10-K report, should be 'yyyy' or 'latest'. Default to 'latest'",
|
70 |
+
] = "latest",
|
71 |
+
) -> str:
|
72 |
+
"""Get the url and filing date of the 10-K report for a given stock and year"""
|
73 |
+
|
74 |
+
url = f"https://financialmodelingprep.com/api/v3/sec_filings/{ticker_symbol}?type=10-k&page=0&apikey={fmp_api_key}"
|
75 |
+
|
76 |
+
# 发送GET请求
|
77 |
+
filing_url = None
|
78 |
+
response = requests.get(url)
|
79 |
+
|
80 |
+
# 确保请求成功
|
81 |
+
if response.status_code == 200:
|
82 |
+
# 解析JSON数据
|
83 |
+
data = response.json()
|
84 |
+
# print(data)
|
85 |
+
if fyear == "latest":
|
86 |
+
filing_url = data[0]["finalLink"]
|
87 |
+
filing_date = data[0]["fillingDate"]
|
88 |
+
else:
|
89 |
+
for filing in data:
|
90 |
+
if filing["fillingDate"].split("-")[0] == fyear:
|
91 |
+
filing_url = filing["finalLink"]
|
92 |
+
filing_date = filing["fillingDate"]
|
93 |
+
break
|
94 |
+
|
95 |
+
return f"Link: {filing_url}\nFiling Date: {filing_date}"
|
96 |
+
else:
|
97 |
+
return f"Failed to retrieve data: {response.status_code}"
|
98 |
+
|
99 |
+
def get_historical_market_cap(
|
100 |
+
ticker_symbol: Annotated[str, "ticker symbol"],
|
101 |
+
date: Annotated[str, "date of the market cap, should be 'yyyy-mm-dd'"],
|
102 |
+
) -> str:
|
103 |
+
"""Get the historical market capitalization for a given stock on a given date"""
|
104 |
+
date = get_next_weekday(date).strftime("%Y-%m-%d")
|
105 |
+
url = f"https://financialmodelingprep.com/api/v3/historical-market-capitalization/{ticker_symbol}?limit=100&from={date}&to={date}&apikey={fmp_api_key}"
|
106 |
+
|
107 |
+
# 发送GET请求
|
108 |
+
mkt_cap = None
|
109 |
+
response = requests.get(url)
|
110 |
+
|
111 |
+
# 确保请求成功
|
112 |
+
if response.status_code == 200:
|
113 |
+
# 解析JSON数据
|
114 |
+
data = response.json()
|
115 |
+
mkt_cap = data[0]["marketCap"]
|
116 |
+
return mkt_cap
|
117 |
+
else:
|
118 |
+
return f"Failed to retrieve data: {response.status_code}"
|
119 |
+
|
120 |
+
def get_historical_bvps(
|
121 |
+
ticker_symbol: Annotated[str, "ticker symbol"],
|
122 |
+
target_date: Annotated[str, "date of the BVPS, should be 'yyyy-mm-dd'"],
|
123 |
+
) -> str:
|
124 |
+
"""Get the historical book value per share for a given stock on a given date"""
|
125 |
+
# 从FMP API获取历史关键财务指标数据
|
126 |
+
url = f"https://financialmodelingprep.com/api/v3/key-metrics/{ticker_symbol}?limit=40&apikey={fmp_api_key}"
|
127 |
+
response = requests.get(url)
|
128 |
+
data = response.json()
|
129 |
+
|
130 |
+
if not data:
|
131 |
+
return "No data available"
|
132 |
+
|
133 |
+
# 找到最接近目标日期的数据
|
134 |
+
closest_data = None
|
135 |
+
min_date_diff = float("inf")
|
136 |
+
target_date = datetime.strptime(target_date, "%Y-%m-%d")
|
137 |
+
for entry in data:
|
138 |
+
date_of_data = datetime.strptime(entry["date"], "%Y-%m-%d")
|
139 |
+
date_diff = abs(target_date - date_of_data).days
|
140 |
+
if date_diff < min_date_diff:
|
141 |
+
min_date_diff = date_diff
|
142 |
+
closest_data = entry
|
143 |
+
|
144 |
+
if closest_data:
|
145 |
+
return closest_data.get("bookValuePerShare", "No BVPS data available")
|
146 |
+
else:
|
147 |
+
return "No close date data found"
|
148 |
+
|
149 |
+
def get_financial_metrics(
|
150 |
+
ticker_symbol: Annotated[str, "ticker symbol"],
|
151 |
+
years: Annotated[int, "number of the years to search from, default to 4"] = 4,
|
152 |
+
):
|
153 |
+
"""Get the financial metrics for a given stock for the last 'years' years"""
|
154 |
+
# Base URL setup for FMP API
|
155 |
+
base_url = "https://financialmodelingprep.com/api/v3"
|
156 |
+
# Create DataFrame
|
157 |
+
df = pd.DataFrame()
|
158 |
+
|
159 |
+
# Iterate over the last 'years' years of data
|
160 |
+
for year_offset in range(years):
|
161 |
+
# Construct URL for income statement and ratios for each year
|
162 |
+
income_statement_url = f"{base_url}/income-statement/{ticker_symbol}?limit={years}&apikey={fmp_api_key}"
|
163 |
+
ratios_url = (
|
164 |
+
f"{base_url}/ratios/{ticker_symbol}?limit={years}&apikey={fmp_api_key}"
|
165 |
+
)
|
166 |
+
key_metrics_url = f"{base_url}/key-metrics/{ticker_symbol}?limit={years}&apikey={fmp_api_key}"
|
167 |
+
|
168 |
+
# Requesting data from the API
|
169 |
+
income_data = requests.get(income_statement_url).json()
|
170 |
+
key_metrics_data = requests.get(key_metrics_url).json()
|
171 |
+
ratios_data = requests.get(ratios_url).json()
|
172 |
+
|
173 |
+
# Extracting needed metrics for each year
|
174 |
+
if income_data and key_metrics_data and ratios_data:
|
175 |
+
metrics = {
|
176 |
+
"Operating Revenue": income_data[year_offset]["revenue"] / 1e6,
|
177 |
+
"Adjusted Net Profit": income_data[year_offset]["netIncome"] / 1e6,
|
178 |
+
"Adjusted EPS": income_data[year_offset]["eps"],
|
179 |
+
"EBIT Margin": ratios_data[year_offset]["ebitPerRevenue"],
|
180 |
+
"ROE": key_metrics_data[year_offset]["roe"],
|
181 |
+
"PE Ratio": ratios_data[year_offset]["priceEarningsRatio"],
|
182 |
+
"EV/EBITDA": key_metrics_data[year_offset][
|
183 |
+
"enterpriseValueOverEBITDA"
|
184 |
+
],
|
185 |
+
"PB Ratio": key_metrics_data[year_offset]["pbRatio"],
|
186 |
+
}
|
187 |
+
# Append the year and metrics to the DataFrame
|
188 |
+
# Extracting the year from the date
|
189 |
+
year = income_data[year_offset]["date"][:4]
|
190 |
+
df[year] = pd.Series(metrics)
|
191 |
+
df = df.sort_index(axis=1)
|
192 |
+
df = df.round(2)
|
193 |
+
return df
|
194 |
+
|
195 |
+
|
196 |
+
if __name__ == "__main__":
|
197 |
+
from finrobot.utils import register_keys_from_json
|
198 |
+
|
199 |
+
register_keys_from_json("config_api_keys")
|
200 |
+
FMPUtils.get_sec_report("MSFT", "2023")
|
finrobot/data_source/sec_utils.py
ADDED
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from sec_api import ExtractorApi
|
3 |
+
from functools import wraps
|
4 |
+
from typing import Annotated
|
5 |
+
from ..utils import SavePathType
|
6 |
+
from ..data_source import FMPUtils
|
7 |
+
|
8 |
+
|
9 |
+
CACHE_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".cache")
|
10 |
+
|
11 |
+
|
12 |
+
def init_sec_api(func):
|
13 |
+
@wraps(func)
|
14 |
+
def wrapper(*args, **kwargs):
|
15 |
+
global extractor
|
16 |
+
if os.environ.get("SEC_API_KEY") is None:
|
17 |
+
print("Please set the environment variable SEC_API_KEY to use sec_api.")
|
18 |
+
return None
|
19 |
+
else:
|
20 |
+
extractor = ExtractorApi(os.environ["SEC_API_KEY"])
|
21 |
+
print("Sec Api initialized")
|
22 |
+
return func(*args, **kwargs)
|
23 |
+
|
24 |
+
return wrapper
|
25 |
+
|
26 |
+
|
27 |
+
class SECUtils:
|
28 |
+
|
29 |
+
@init_sec_api
|
30 |
+
def get_10k_section(
|
31 |
+
ticker_symbol: Annotated[str, "ticker symbol"],
|
32 |
+
fyear: Annotated[str, "fiscal year of the 10-K report"],
|
33 |
+
section: Annotated[
|
34 |
+
str | int,
|
35 |
+
"Section of the 10-K report to extract, should be in [1, 1A, 1B, 2, 3, 4, 5, 6, 7, 7A, 8, 9, 9A, 9B, 10, 11, 12, 13, 14, 15]",
|
36 |
+
],
|
37 |
+
report_address: Annotated[
|
38 |
+
str,
|
39 |
+
"URL of the 10-K report, if not specified, will get report url from fmp api",
|
40 |
+
] = None,
|
41 |
+
save_path: SavePathType = None,
|
42 |
+
) -> str:
|
43 |
+
"""
|
44 |
+
Get a specific section of a 10-K report from the SEC API.
|
45 |
+
"""
|
46 |
+
if isinstance(section, int):
|
47 |
+
section = str(section)
|
48 |
+
if section not in [
|
49 |
+
"1A",
|
50 |
+
"1B",
|
51 |
+
"7A",
|
52 |
+
"9A",
|
53 |
+
"9B",
|
54 |
+
] + [str(i) for i in range(1, 16)]:
|
55 |
+
raise ValueError(
|
56 |
+
"Section must be in [1, 1A, 1B, 2, 3, 4, 5, 6, 7, 7A, 8, 9, 9A, 9B, 10, 11, 12, 13, 14, 15]"
|
57 |
+
)
|
58 |
+
|
59 |
+
# os.makedirs(f"{self.project_dir}/10k", exist_ok=True)
|
60 |
+
|
61 |
+
# report_name = f"{self.project_dir}/10k/section_{section}.txt"
|
62 |
+
|
63 |
+
# if USE_CACHE and os.path.exists(report_name):
|
64 |
+
# with open(report_name, "r") as f:
|
65 |
+
# section_text = f.read()
|
66 |
+
# else:
|
67 |
+
if report_address is None:
|
68 |
+
report_address = FMPUtils.get_sec_report(ticker_symbol, fyear)
|
69 |
+
if report_address.startswith("Link: "):
|
70 |
+
report_address = report_address.lstrip("Link: ").split()[0]
|
71 |
+
else:
|
72 |
+
return report_address # debug info
|
73 |
+
|
74 |
+
cache_path = os.path.join(CACHE_PATH, f"sec_utils/{ticker_symbol}_{fyear}_{section}.txt")
|
75 |
+
if os.path.exists(cache_path):
|
76 |
+
with open(cache_path, "r") as f:
|
77 |
+
section_text = f.read()
|
78 |
+
else:
|
79 |
+
section_text = extractor.get_section(report_address, section, "text")
|
80 |
+
os.makedirs(os.path.dirname(cache_path), exist_ok=True)
|
81 |
+
with open(cache_path, "w") as f:
|
82 |
+
f.write(section_text)
|
83 |
+
|
84 |
+
if save_path:
|
85 |
+
os.makedirs(os.path.dirname(save_path), exist_ok=True)
|
86 |
+
with open(save_path, "w") as f:
|
87 |
+
f.write(section_text)
|
88 |
+
|
89 |
+
return section_text
|
finrobot/data_source/yfinance_utils.py
ADDED
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import yfinance as yf
|
2 |
+
from typing import Annotated, Callable, Any, Optional
|
3 |
+
from pandas import DataFrame
|
4 |
+
from functools import wraps
|
5 |
+
|
6 |
+
from ..utils import save_output, SavePathType, decorate_all_methods
|
7 |
+
|
8 |
+
|
9 |
+
def init_ticker(func: Callable) -> Callable:
|
10 |
+
"""Decorator to initialize yf.Ticker and pass it to the function."""
|
11 |
+
|
12 |
+
@wraps(func)
|
13 |
+
def wrapper(symbol: Annotated[str, "ticker symbol"], *args, **kwargs) -> Any:
|
14 |
+
ticker = yf.Ticker(symbol)
|
15 |
+
return func(ticker, *args, **kwargs)
|
16 |
+
|
17 |
+
return wrapper
|
18 |
+
|
19 |
+
|
20 |
+
@decorate_all_methods(init_ticker)
|
21 |
+
class YFinanceUtils:
|
22 |
+
|
23 |
+
def get_stock_data(
|
24 |
+
symbol: Annotated[str, "ticker symbol"],
|
25 |
+
start_date: Annotated[
|
26 |
+
str, "start date for retrieving stock price data, YYYY-mm-dd"
|
27 |
+
],
|
28 |
+
end_date: Annotated[
|
29 |
+
str, "end date for retrieving stock price data, YYYY-mm-dd"
|
30 |
+
],
|
31 |
+
save_path: SavePathType = None,
|
32 |
+
) -> DataFrame:
|
33 |
+
ticker = symbol
|
34 |
+
stock_data = ticker.history(start=start_date, end=end_date)
|
35 |
+
save_output(stock_data, f"Stock data for {ticker.ticker}", save_path)
|
36 |
+
return stock_data
|
37 |
+
|
38 |
+
def get_stock_info(
|
39 |
+
symbol: Annotated[str, "ticker symbol"],
|
40 |
+
) -> dict:
|
41 |
+
"""Fetches and returns stock information."""
|
42 |
+
ticker = symbol
|
43 |
+
stock_info = ticker.info
|
44 |
+
return stock_info
|
45 |
+
|
46 |
+
def get_company_info(
|
47 |
+
self,
|
48 |
+
symbol: Annotated[str, "ticker symbol"],
|
49 |
+
save_path: Optional[str] = None,
|
50 |
+
) -> DataFrame:
|
51 |
+
"""Fetches and returns company information as a DataFrame."""
|
52 |
+
ticker = symbol
|
53 |
+
info = ticker.info
|
54 |
+
company_info = {
|
55 |
+
"Company Name": info.get("shortName", "N/A"),
|
56 |
+
"Industry": info.get("industry", "N/A"),
|
57 |
+
"Sector": info.get("sector", "N/A"),
|
58 |
+
"Country": info.get("country", "N/A"),
|
59 |
+
"Website": info.get("website", "N/A"),
|
60 |
+
}
|
61 |
+
company_info_df = DataFrame([company_info])
|
62 |
+
if save_path:
|
63 |
+
company_info_df.to_csv(save_path)
|
64 |
+
print(f"Company info for {ticker.ticker} saved to {save_path}")
|
65 |
+
return company_info_df
|
66 |
+
|
67 |
+
def get_stock_dividends(
|
68 |
+
self,
|
69 |
+
symbol: Annotated[str, "ticker symbol"],
|
70 |
+
save_path: Optional[str] = None,
|
71 |
+
) -> DataFrame:
|
72 |
+
"""Fetches and returns the dividends data as a DataFrame."""
|
73 |
+
ticker = symbol
|
74 |
+
dividends = ticker.dividends
|
75 |
+
if save_path:
|
76 |
+
dividends.to_csv(save_path)
|
77 |
+
print(f"Dividends for {ticker.ticker} saved to {save_path}")
|
78 |
+
return dividends
|
79 |
+
|
80 |
+
def get_income_stmt(symbol: Annotated[str, "ticker symbol"]) -> DataFrame:
|
81 |
+
"""Fetches and returns the income statement of the company as a DataFrame."""
|
82 |
+
ticker = symbol
|
83 |
+
income_stmt = ticker.financials
|
84 |
+
return income_stmt
|
85 |
+
|
86 |
+
def get_balance_sheet(symbol: Annotated[str, "ticker symbol"]) -> DataFrame:
|
87 |
+
"""Fetches and returns the balance sheet of the company as a DataFrame."""
|
88 |
+
ticker = symbol
|
89 |
+
balance_sheet = ticker.balance_sheet
|
90 |
+
return balance_sheet
|
91 |
+
|
92 |
+
def get_cash_flow(symbol: Annotated[str, "ticker symbol"]) -> DataFrame:
|
93 |
+
"""Fetches and returns the cash flow statement of the company as a DataFrame."""
|
94 |
+
ticker = symbol
|
95 |
+
cash_flow = ticker.cashflow
|
96 |
+
return cash_flow
|
97 |
+
|
98 |
+
def get_analyst_recommendations(symbol: Annotated[str, "ticker symbol"]) -> tuple:
|
99 |
+
"""Fetches the latest analyst recommendations and returns the most common recommendation and its count."""
|
100 |
+
ticker = symbol
|
101 |
+
recommendations = ticker.recommendations
|
102 |
+
if recommendations.empty:
|
103 |
+
return None, 0 # No recommendations available
|
104 |
+
|
105 |
+
# Assuming 'period' column exists and needs to be excluded
|
106 |
+
row_0 = recommendations.iloc[0, 1:] # Exclude 'period' column if necessary
|
107 |
+
|
108 |
+
# Find the maximum voting result
|
109 |
+
max_votes = row_0.max()
|
110 |
+
majority_voting_result = row_0[row_0 == max_votes].index.tolist()
|
111 |
+
|
112 |
+
return majority_voting_result[0], max_votes
|
113 |
+
|
114 |
+
|
115 |
+
if __name__ == "__main__":
|
116 |
+
print(YFinanceUtils.get_stock_data("AAPL", "2021-01-01", "2021-12-31"))
|
117 |
+
# print(YFinanceUtils.get_stock_data())
|
finrobot/functional/__init__.py
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from .analyzer import ReportAnalysisUtils
|
2 |
+
from .charting import MplFinanceUtils, ReportChartUtils
|
3 |
+
from .coding import CodingUtils, IPythonUtils
|
4 |
+
from .quantitative import BackTraderUtils
|
5 |
+
from .reportlab import ReportLabUtils
|
6 |
+
from .text import TextUtils
|
finrobot/functional/analyzer.py
ADDED
@@ -0,0 +1,344 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from textwrap import dedent
|
3 |
+
from typing import Annotated
|
4 |
+
from datetime import timedelta, datetime
|
5 |
+
from ..data_source import YFinanceUtils, SECUtils, FMPUtils
|
6 |
+
|
7 |
+
|
8 |
+
def combine_prompt(instruction, resource, table_str=None):
|
9 |
+
if table_str:
|
10 |
+
prompt = f"{table_str}\n\nResource: {resource}\n\nInstruction: {instruction}"
|
11 |
+
else:
|
12 |
+
prompt = f"Resource: {resource}\n\nInstruction: {instruction}"
|
13 |
+
return prompt
|
14 |
+
|
15 |
+
|
16 |
+
def save_to_file(data: str, file_path: str):
|
17 |
+
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
18 |
+
with open(file_path, "w") as f:
|
19 |
+
f.write(data)
|
20 |
+
|
21 |
+
|
22 |
+
class ReportAnalysisUtils:
|
23 |
+
|
24 |
+
def analyze_income_stmt(
|
25 |
+
ticker_symbol: Annotated[str, "ticker symbol"],
|
26 |
+
fyear: Annotated[str, "fiscal year of the 10-K report"],
|
27 |
+
save_path: Annotated[str, "txt file path, to which the returned instruction & resources are written."]
|
28 |
+
) -> str:
|
29 |
+
"""
|
30 |
+
Retrieve the income statement for the given ticker symbol with the related section of its 10-K report.
|
31 |
+
Then return with an instruction on how to analyze the income statement.
|
32 |
+
"""
|
33 |
+
# Retrieve the income statement
|
34 |
+
income_stmt = YFinanceUtils.get_income_stmt(ticker_symbol)
|
35 |
+
df_string = "Income statement:\n" + income_stmt.to_string().strip()
|
36 |
+
|
37 |
+
# Analysis instruction
|
38 |
+
instruction = dedent(
|
39 |
+
"""
|
40 |
+
Conduct a comprehensive analysis of the company's income statement for the current fiscal year.
|
41 |
+
Start with an overall revenue record, including Year-over-Year or Quarter-over-Quarter comparisons,
|
42 |
+
and break down revenue sources to identify primary contributors and trends. Examine the Cost of
|
43 |
+
Goods Sold for potential cost control issues. Review profit margins such as gross, operating,
|
44 |
+
and net profit margins to evaluate cost efficiency, operational effectiveness, and overall profitability.
|
45 |
+
Analyze Earnings Per Share to understand investor perspectives. Compare these metrics with historical
|
46 |
+
data and industry or competitor benchmarks to identify growth patterns, profitability trends, and
|
47 |
+
operational challenges. The output should be a strategic overview of the company’s financial health
|
48 |
+
in a single paragraph, less than 130 words, summarizing the previous analysis into 4-5 key points under
|
49 |
+
respective subheadings with specific discussion and strong data support.
|
50 |
+
"""
|
51 |
+
)
|
52 |
+
|
53 |
+
# Retrieve the related section from the 10-K report
|
54 |
+
section_text = SECUtils.get_10k_section(ticker_symbol, fyear, 7)
|
55 |
+
|
56 |
+
# Combine the instruction, section text, and income statement
|
57 |
+
prompt = combine_prompt(instruction, section_text, df_string)
|
58 |
+
|
59 |
+
save_to_file(prompt, save_path)
|
60 |
+
return f"instruction & resources saved to {save_path}"
|
61 |
+
|
62 |
+
def analyze_balance_sheet(
|
63 |
+
ticker_symbol: Annotated[str, "ticker symbol"],
|
64 |
+
fyear: Annotated[str, "fiscal year of the 10-K report"],
|
65 |
+
save_path: Annotated[str, "txt file path, to which the returned instruction & resources are written."]
|
66 |
+
) -> str:
|
67 |
+
"""
|
68 |
+
Retrieve the balance sheet for the given ticker symbol with the related section of its 10-K report.
|
69 |
+
Then return with an instruction on how to analyze the balance sheet.
|
70 |
+
"""
|
71 |
+
balance_sheet = YFinanceUtils.get_balance_sheet(ticker_symbol)
|
72 |
+
df_string = "Balance sheet:\n" + balance_sheet.to_string().strip()
|
73 |
+
|
74 |
+
instruction = dedent(
|
75 |
+
"""
|
76 |
+
Delve into a detailed scrutiny of the company's balance sheet for the most recent fiscal year, pinpointing
|
77 |
+
the structure of assets, liabilities, and shareholders' equity to decode the firm's financial stability and
|
78 |
+
operational efficiency. Focus on evaluating the liquidity through current assets versus current liabilities,
|
79 |
+
the solvency via long-term debt ratios, and the equity position to gauge long-term investment potential.
|
80 |
+
Contrast these metrics with previous years' data to highlight financial trends, improvements, or deteriorations.
|
81 |
+
Finalize with a strategic assessment of the company's financial leverage, asset management, and capital structure,
|
82 |
+
providing insights into its fiscal health and future prospects in a single paragraph. Less than 130 words.
|
83 |
+
"""
|
84 |
+
)
|
85 |
+
|
86 |
+
section_text = SECUtils.get_10k_section(ticker_symbol, fyear, 7)
|
87 |
+
prompt = combine_prompt(instruction, section_text, df_string)
|
88 |
+
save_to_file(prompt, save_path)
|
89 |
+
return f"instruction & resources saved to {save_path}"
|
90 |
+
|
91 |
+
def analyze_cash_flow(
|
92 |
+
ticker_symbol: Annotated[str, "ticker symbol"],
|
93 |
+
fyear: Annotated[str, "fiscal year of the 10-K report"],
|
94 |
+
save_path: Annotated[str, "txt file path, to which the returned instruction & resources are written."]
|
95 |
+
) -> str:
|
96 |
+
"""
|
97 |
+
Retrieve the cash flow statement for the given ticker symbol with the related section of its 10-K report.
|
98 |
+
Then return with an instruction on how to analyze the cash flow statement.
|
99 |
+
"""
|
100 |
+
cash_flow = YFinanceUtils.get_cash_flow(ticker_symbol)
|
101 |
+
df_string = "Cash flow statement:\n" + cash_flow.to_string().strip()
|
102 |
+
|
103 |
+
instruction = dedent(
|
104 |
+
"""
|
105 |
+
Dive into a comprehensive evaluation of the company's cash flow for the latest fiscal year, focusing on cash inflows
|
106 |
+
and outflows across operating, investing, and financing activities. Examine the operational cash flow to assess the
|
107 |
+
core business profitability, scrutinize investing activities for insights into capital expenditures and investments,
|
108 |
+
and review financing activities to understand debt, equity movements, and dividend policies. Compare these cash movements
|
109 |
+
to prior periods to discern trends, sustainability, and liquidity risks. Conclude with an informed analysis of the company's
|
110 |
+
cash management effectiveness, liquidity position, and potential for future growth or financial challenges in a single paragraph.
|
111 |
+
Less than 130 words.
|
112 |
+
"""
|
113 |
+
)
|
114 |
+
|
115 |
+
section_text = SECUtils.get_10k_section(ticker_symbol, fyear, 7)
|
116 |
+
prompt = combine_prompt(instruction, section_text, df_string)
|
117 |
+
save_to_file(prompt, save_path)
|
118 |
+
return f"instruction & resources saved to {save_path}"
|
119 |
+
|
120 |
+
def analyze_segment_stmt(
|
121 |
+
ticker_symbol: Annotated[str, "ticker symbol"],
|
122 |
+
fyear: Annotated[str, "fiscal year of the 10-K report"],
|
123 |
+
save_path: Annotated[str, "txt file path, to which the returned instruction & resources are written."]
|
124 |
+
) -> str:
|
125 |
+
"""
|
126 |
+
Retrieve the income statement and the related section of its 10-K report for the given ticker symbol.
|
127 |
+
Then return with an instruction on how to create a segment analysis.
|
128 |
+
"""
|
129 |
+
income_stmt = YFinanceUtils.get_income_stmt(ticker_symbol)
|
130 |
+
df_string = (
|
131 |
+
"Income statement (Segment Analysis):\n" + income_stmt.to_string().strip()
|
132 |
+
)
|
133 |
+
|
134 |
+
instruction = dedent(
|
135 |
+
"""
|
136 |
+
Identify the company's business segments and create a segment analysis using the Management's Discussion and Analysis
|
137 |
+
and the income statement, subdivided by segment with clear headings. Address revenue and net profit with specific data,
|
138 |
+
and calculate the changes. Detail strategic partnerships and their impacts, including details like the companies or organizations.
|
139 |
+
Describe product innovations and their effects on income growth. Quantify market share and its changes, or state market position
|
140 |
+
and its changes. Analyze market dynamics and profit challenges, noting any effects from national policy changes. Include the cost side,
|
141 |
+
detailing operational costs, innovation investments, and expenses from channel expansion, etc. Support each statement with evidence,
|
142 |
+
keeping each segment analysis concise and under 60 words, accurately sourcing information. For each segment, consolidate the most
|
143 |
+
significant findings into one clear, concise paragraph, excluding less critical or vaguely described aspects to ensure clarity and
|
144 |
+
reliance on evidence-backed information. For each segment, the output should be one single paragraph within 150 words.
|
145 |
+
"""
|
146 |
+
)
|
147 |
+
section_text = SECUtils.get_10k_section(ticker_symbol, fyear, 7)
|
148 |
+
prompt = combine_prompt(instruction, section_text, df_string)
|
149 |
+
save_to_file(prompt, save_path)
|
150 |
+
return f"instruction & resources saved to {save_path}"
|
151 |
+
|
152 |
+
def income_summarization(
|
153 |
+
ticker_symbol: Annotated[str, "ticker symbol"],
|
154 |
+
fyear: Annotated[str, "fiscal year of the 10-K report"],
|
155 |
+
income_stmt_analysis: Annotated[str, "in-depth income statement analysis"],
|
156 |
+
segment_analysis: Annotated[str, "in-depth segment analysis"],
|
157 |
+
save_path: Annotated[str, "txt file path, to which the returned instruction & resources are written."]
|
158 |
+
) -> str:
|
159 |
+
"""
|
160 |
+
With the income statement and segment analysis for the given ticker symbol.
|
161 |
+
Then return with an instruction on how to synthesize these analyses into a single coherent paragraph.
|
162 |
+
"""
|
163 |
+
# income_stmt_analysis = analyze_income_stmt(ticker_symbol)
|
164 |
+
# segment_analysis = analyze_segment_stmt(ticker_symbol)
|
165 |
+
|
166 |
+
instruction = dedent(
|
167 |
+
f"""
|
168 |
+
Income statement analysis: {income_stmt_analysis},
|
169 |
+
Segment analysis: {segment_analysis},
|
170 |
+
Synthesize the findings from the in-depth income statement analysis and segment analysis into a single, coherent paragraph.
|
171 |
+
It should be fact-based and data-driven. First, present and assess overall revenue and profit situation, noting significant
|
172 |
+
trends and changes. Second, examine the performance of the various business segments, with an emphasis on their revenue and
|
173 |
+
profit changes, revenue contributions and market dynamics. For information not covered in the first two areas, identify and
|
174 |
+
integrate key findings related to operation, potential risks and strategic opportunities for growth and stability into the analysis.
|
175 |
+
For each part, integrate historical data comparisons and provide relevant facts, metrics or data as evidence. The entire synthesis
|
176 |
+
should be presented as a continuous paragraph without the use of bullet points. Use subtitles and numbering for each key point.
|
177 |
+
The total output should be less than 160 words.
|
178 |
+
"""
|
179 |
+
)
|
180 |
+
|
181 |
+
section_text = SECUtils.get_10k_section(ticker_symbol, fyear, 7)
|
182 |
+
prompt = combine_prompt(instruction, section_text, "")
|
183 |
+
save_to_file(prompt, save_path)
|
184 |
+
return f"instruction & resources saved to {save_path}"
|
185 |
+
|
186 |
+
def get_risk_assessment(
|
187 |
+
ticker_symbol: Annotated[str, "ticker symbol"],
|
188 |
+
fyear: Annotated[str, "fiscal year of the 10-K report"],
|
189 |
+
save_path: Annotated[str, "txt file path, to which the returned instruction & resources are written."]
|
190 |
+
) -> str:
|
191 |
+
"""
|
192 |
+
Retrieve the risk factors for the given ticker symbol with the related section of its 10-K report.
|
193 |
+
Then return with an instruction on how to summarize the top 3 key risks of the company.
|
194 |
+
"""
|
195 |
+
company_name = YFinanceUtils.get_stock_info(ticker_symbol)["shortName"]
|
196 |
+
risk_factors = SECUtils.get_10k_section(ticker_symbol, fyear, "1A")
|
197 |
+
section_text = (
|
198 |
+
"Company Name: "
|
199 |
+
+ company_name
|
200 |
+
+ "\n\n"
|
201 |
+
+ "Risk factors:\n"
|
202 |
+
+ risk_factors
|
203 |
+
+ "\n\n"
|
204 |
+
)
|
205 |
+
instruction = "According to the given information, summarize the top 3 key risks of the company. Less than 100 words."
|
206 |
+
prompt = combine_prompt(instruction, section_text, "")
|
207 |
+
save_to_file(prompt, save_path)
|
208 |
+
return f"instruction & resources saved to {save_path}"
|
209 |
+
|
210 |
+
def analyze_business_highlights(
|
211 |
+
ticker_symbol: Annotated[str, "ticker symbol"],
|
212 |
+
fyear: Annotated[str, "fiscal year of the 10-K report"],
|
213 |
+
save_path: Annotated[str, "txt file path, to which the returned instruction & resources are written."]
|
214 |
+
) -> str:
|
215 |
+
"""
|
216 |
+
Retrieve the business summary and related section of its 10-K report for the given ticker symbol.
|
217 |
+
Then return with an instruction on how to describe the performance highlights per business of the company.
|
218 |
+
"""
|
219 |
+
business_summary = SECUtils.get_10k_section(ticker_symbol, fyear, 1)
|
220 |
+
section_7 = SECUtils.get_10k_section(ticker_symbol, fyear, 7)
|
221 |
+
section_text = (
|
222 |
+
"Business summary:\n"
|
223 |
+
+ business_summary
|
224 |
+
+ "\n\n"
|
225 |
+
+ "Management's Discussion and Analysis of Financial Condition and Results of Operations:\n"
|
226 |
+
+ section_7
|
227 |
+
)
|
228 |
+
instruction = dedent(
|
229 |
+
"""
|
230 |
+
According to the given information, describe the performance highlights per business of the company.
|
231 |
+
Each business description should contain one sentence of a summarization and one sentence of explanation.
|
232 |
+
Less than 130 words.
|
233 |
+
"""
|
234 |
+
)
|
235 |
+
prompt = combine_prompt(instruction, section_text, "")
|
236 |
+
save_to_file(prompt, save_path)
|
237 |
+
return f"instruction & resources saved to {save_path}"
|
238 |
+
|
239 |
+
def analyze_company_description(
|
240 |
+
ticker_symbol: Annotated[str, "ticker symbol"],
|
241 |
+
fyear: Annotated[str, "fiscal year of the 10-K report"],
|
242 |
+
save_path: Annotated[str, "txt file path, to which the returned instruction & resources are written."]
|
243 |
+
) -> str:
|
244 |
+
"""
|
245 |
+
Retrieve the company description and related sections of its 10-K report for the given ticker symbol.
|
246 |
+
Then return with an instruction on how to describe the company's industry, strengths, trends, and strategic initiatives.
|
247 |
+
"""
|
248 |
+
company_name = YFinanceUtils.get_stock_info(ticker_symbol).get(
|
249 |
+
"shortName", "N/A"
|
250 |
+
)
|
251 |
+
business_summary = SECUtils.get_10k_section(ticker_symbol, fyear, 1)
|
252 |
+
section_7 = SECUtils.get_10k_section(ticker_symbol, fyear, 7)
|
253 |
+
section_text = (
|
254 |
+
"Company Name: "
|
255 |
+
+ company_name
|
256 |
+
+ "\n\n"
|
257 |
+
+ "Business summary:\n"
|
258 |
+
+ business_summary
|
259 |
+
+ "\n\n"
|
260 |
+
+ "Management's Discussion and Analysis of Financial Condition and Results of Operations:\n"
|
261 |
+
+ section_7
|
262 |
+
)
|
263 |
+
instruction = dedent(
|
264 |
+
"""
|
265 |
+
According to the given information,
|
266 |
+
1. Briefly describe the company’s industry,
|
267 |
+
2. Highlight core strengths and competitive advantages key products or services,
|
268 |
+
3. Identify current industry trends, opportunities, and challenges that influence the company’s strategy,
|
269 |
+
4. Outline recent strategic initiatives such as product launches, acquisitions, or new partnerships, and describe the company's response to market conditions.
|
270 |
+
Less than 400 words.
|
271 |
+
"""
|
272 |
+
)
|
273 |
+
step_prompt = combine_prompt(instruction, section_text, "")
|
274 |
+
instruction2 = "Summarize the analysis, less than 130 words."
|
275 |
+
prompt = combine_prompt(instruction=instruction2, resource=step_prompt)
|
276 |
+
save_to_file(prompt, save_path)
|
277 |
+
return f"instruction & resources saved to {save_path}"
|
278 |
+
|
279 |
+
def get_key_data(
|
280 |
+
ticker_symbol: Annotated[str, "ticker symbol"],
|
281 |
+
filing_date: Annotated[
|
282 |
+
str | datetime, "the filing date of the financial report being analyzed"
|
283 |
+
],
|
284 |
+
) -> dict:
|
285 |
+
"""
|
286 |
+
return key financial data used in annual report for the given ticker symbol and filing date
|
287 |
+
"""
|
288 |
+
|
289 |
+
if not isinstance(filing_date, datetime):
|
290 |
+
filing_date = datetime.strptime(filing_date, "%Y-%m-%d")
|
291 |
+
|
292 |
+
# Fetch historical market data for the past 6 months
|
293 |
+
start = (filing_date - timedelta(weeks=52)).strftime("%Y-%m-%d")
|
294 |
+
end = filing_date.strftime("%Y-%m-%d")
|
295 |
+
|
296 |
+
hist = YFinanceUtils.get_stock_data(ticker_symbol, start, end)
|
297 |
+
|
298 |
+
# 获取其他相关信息
|
299 |
+
info = YFinanceUtils.get_stock_info(ticker_symbol)
|
300 |
+
close_price = hist["Close"].iloc[-1]
|
301 |
+
|
302 |
+
# Calculate the average daily trading volume
|
303 |
+
six_months_start = (filing_date - timedelta(weeks=26)).strftime("%Y-%m-%d")
|
304 |
+
hist_last_6_months = hist[
|
305 |
+
(hist.index >= six_months_start) & (hist.index <= end)
|
306 |
+
]
|
307 |
+
|
308 |
+
# 计算这6个月的平均每日交易量
|
309 |
+
avg_daily_volume_6m = (
|
310 |
+
hist_last_6_months["Volume"].mean()
|
311 |
+
if not hist_last_6_months["Volume"].empty
|
312 |
+
else 0
|
313 |
+
)
|
314 |
+
|
315 |
+
fiftyTwoWeekLow = hist["High"].min()
|
316 |
+
fiftyTwoWeekHigh = hist["Low"].max()
|
317 |
+
|
318 |
+
# avg_daily_volume_6m = hist['Volume'].mean()
|
319 |
+
|
320 |
+
# convert back to str for function calling
|
321 |
+
filing_date = filing_date.strftime("%Y-%m-%d")
|
322 |
+
|
323 |
+
# Print the result
|
324 |
+
# print(f"Over the past 6 months, the average daily trading volume for {ticker_symbol} was: {avg_daily_volume_6m:.2f}")
|
325 |
+
rating, _ = YFinanceUtils.get_analyst_recommendations(ticker_symbol)
|
326 |
+
target_price = FMPUtils.get_target_price(ticker_symbol, filing_date)
|
327 |
+
result = {
|
328 |
+
"Rating": rating,
|
329 |
+
"Target Price": target_price,
|
330 |
+
f"6m avg daily vol ({info['currency']}mn)": "{:.2f}".format(
|
331 |
+
avg_daily_volume_6m / 1e6
|
332 |
+
),
|
333 |
+
f"Closing Price ({info['currency']})": "{:.2f}".format(close_price),
|
334 |
+
f"Market Cap ({info['currency']}mn)": "{:.2f}".format(
|
335 |
+
FMPUtils.get_historical_market_cap(ticker_symbol, filing_date) / 1e6
|
336 |
+
),
|
337 |
+
f"52 Week Price Range ({info['currency']})": "{:.2f} - {:.2f}".format(
|
338 |
+
fiftyTwoWeekLow, fiftyTwoWeekHigh
|
339 |
+
),
|
340 |
+
f"BVPS ({info['currency']})": "{:.2f}".format(
|
341 |
+
FMPUtils.get_historical_bvps(ticker_symbol, filing_date)
|
342 |
+
),
|
343 |
+
}
|
344 |
+
return result
|
finrobot/functional/charting.py
ADDED
@@ -0,0 +1,235 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import mplfinance as mpf
|
3 |
+
import pandas as pd
|
4 |
+
|
5 |
+
from matplotlib import pyplot as plt
|
6 |
+
from typing import Annotated, List, Tuple
|
7 |
+
from pandas import DateOffset
|
8 |
+
from datetime import datetime, timedelta
|
9 |
+
|
10 |
+
from ..data_source.yfinance_utils import YFinanceUtils
|
11 |
+
|
12 |
+
|
13 |
+
class MplFinanceUtils:
|
14 |
+
|
15 |
+
def plot_stock_price_chart(
|
16 |
+
ticker_symbol: Annotated[
|
17 |
+
str, "Ticker symbol of the stock (e.g., 'AAPL' for Apple)"
|
18 |
+
],
|
19 |
+
start_date: Annotated[
|
20 |
+
str, "Start date of the historical data in 'YYYY-MM-DD' format"
|
21 |
+
],
|
22 |
+
end_date: Annotated[
|
23 |
+
str, "End date of the historical data in 'YYYY-MM-DD' format"
|
24 |
+
],
|
25 |
+
save_path: Annotated[str, "File path where the plot should be saved"],
|
26 |
+
verbose: Annotated[
|
27 |
+
str, "Whether to print stock data to console. Default to False."
|
28 |
+
] = False,
|
29 |
+
type: Annotated[
|
30 |
+
str,
|
31 |
+
"Type of the plot, should be one of 'candle','ohlc','line','renko','pnf','hollow_and_filled'. Default to 'candle'",
|
32 |
+
] = "candle",
|
33 |
+
style: Annotated[
|
34 |
+
str,
|
35 |
+
"Style of the plot, should be one of 'default','classic','charles','yahoo','nightclouds','sas','blueskies','mike'. Default to 'default'.",
|
36 |
+
] = "default",
|
37 |
+
mav: Annotated[
|
38 |
+
int | List[int] | Tuple[int, ...] | None,
|
39 |
+
"Moving average window(s) to plot on the chart. Default to None.",
|
40 |
+
] = None,
|
41 |
+
show_nontrading: Annotated[
|
42 |
+
bool, "Whether to show non-trading days on the chart. Default to False."
|
43 |
+
] = False,
|
44 |
+
) -> str:
|
45 |
+
"""
|
46 |
+
Plot a stock price chart using mplfinance for the specified stock and time period,
|
47 |
+
and save the plot to a file.
|
48 |
+
"""
|
49 |
+
# Fetch historical data
|
50 |
+
stock_data = YFinanceUtils.get_stock_data(ticker_symbol, start_date, end_date)
|
51 |
+
if verbose:
|
52 |
+
print(stock_data.to_string())
|
53 |
+
|
54 |
+
params = {
|
55 |
+
"type": type,
|
56 |
+
"style": style,
|
57 |
+
"title": f"{ticker_symbol} {type} chart",
|
58 |
+
"ylabel": "Price",
|
59 |
+
"volume": True,
|
60 |
+
"ylabel_lower": "Volume",
|
61 |
+
"mav": mav,
|
62 |
+
"show_nontrading": show_nontrading,
|
63 |
+
"savefig": save_path,
|
64 |
+
}
|
65 |
+
# Using dictionary comprehension to filter out None values (MplFinance does not accept None values)
|
66 |
+
filtered_params = {k: v for k, v in params.items() if v is not None}
|
67 |
+
|
68 |
+
# Plot chart
|
69 |
+
mpf.plot(stock_data, **filtered_params)
|
70 |
+
|
71 |
+
return f"{type} chart saved to <img {save_path}>"
|
72 |
+
|
73 |
+
|
74 |
+
class ReportChartUtils:
|
75 |
+
|
76 |
+
def get_share_performance(
|
77 |
+
ticker_symbol: Annotated[
|
78 |
+
str, "Ticker symbol of the stock (e.g., 'AAPL' for Apple)"
|
79 |
+
],
|
80 |
+
filing_date: Annotated[str | datetime, "filing date in 'YYYY-MM-DD' format"],
|
81 |
+
save_path: Annotated[str, "File path where the plot should be saved"],
|
82 |
+
) -> str:
|
83 |
+
"""Plot the stock performance of a company compared to the S&P 500 over the past year."""
|
84 |
+
if isinstance(filing_date, str):
|
85 |
+
filing_date = datetime.strptime(filing_date, "%Y-%m-%d")
|
86 |
+
|
87 |
+
def fetch_stock_data(ticker):
|
88 |
+
start = (filing_date - timedelta(days=365)).strftime("%Y-%m-%d")
|
89 |
+
end = filing_date.strftime("%Y-%m-%d")
|
90 |
+
historical_data = YFinanceUtils.get_stock_data(ticker, start, end)
|
91 |
+
# hist = stock.history(period=period)
|
92 |
+
return historical_data["Close"]
|
93 |
+
|
94 |
+
target_close = fetch_stock_data(ticker_symbol)
|
95 |
+
sp500_close = fetch_stock_data("^GSPC")
|
96 |
+
info = YFinanceUtils.get_stock_info(ticker_symbol)
|
97 |
+
|
98 |
+
# 计算变化率
|
99 |
+
company_change = (
|
100 |
+
(target_close - target_close.iloc[0]) / target_close.iloc[0] * 100
|
101 |
+
)
|
102 |
+
sp500_change = (sp500_close - sp500_close.iloc[0]) / sp500_close.iloc[0] * 100
|
103 |
+
|
104 |
+
# 计算额外的日期点
|
105 |
+
start_date = company_change.index.min()
|
106 |
+
four_months = start_date + DateOffset(months=4)
|
107 |
+
eight_months = start_date + DateOffset(months=8)
|
108 |
+
end_date = company_change.index.max()
|
109 |
+
|
110 |
+
# 准备绘图
|
111 |
+
plt.rcParams.update({"font.size": 20}) # 调整为更大的字体大小
|
112 |
+
plt.figure(figsize=(14, 7))
|
113 |
+
plt.plot(
|
114 |
+
company_change.index,
|
115 |
+
company_change,
|
116 |
+
label=f'{info["shortName"]} Change %',
|
117 |
+
color="blue",
|
118 |
+
)
|
119 |
+
plt.plot(
|
120 |
+
sp500_change.index, sp500_change, label="S&P 500 Change %", color="red"
|
121 |
+
)
|
122 |
+
|
123 |
+
# 设置标题和标签
|
124 |
+
plt.title(f'{info["shortName"]} vs S&P 500 - Change % Over the Past Year')
|
125 |
+
plt.xlabel("Date")
|
126 |
+
plt.ylabel("Change %")
|
127 |
+
|
128 |
+
# 设置x轴刻度标签
|
129 |
+
plt.xticks(
|
130 |
+
[start_date, four_months, eight_months, end_date],
|
131 |
+
[
|
132 |
+
start_date.strftime("%Y-%m"),
|
133 |
+
four_months.strftime("%Y-%m"),
|
134 |
+
eight_months.strftime("%Y-%m"),
|
135 |
+
end_date.strftime("%Y-%m"),
|
136 |
+
],
|
137 |
+
)
|
138 |
+
|
139 |
+
plt.legend()
|
140 |
+
plt.grid(True)
|
141 |
+
plt.tight_layout()
|
142 |
+
# plt.show()
|
143 |
+
plot_path = (
|
144 |
+
f"{save_path}/stock_performance.png"
|
145 |
+
if os.path.isdir(save_path)
|
146 |
+
else save_path
|
147 |
+
)
|
148 |
+
plt.savefig(plot_path)
|
149 |
+
plt.close()
|
150 |
+
return f"last year stock performance chart saved to <img {plot_path}>"
|
151 |
+
|
152 |
+
def get_pe_eps_performance(
|
153 |
+
ticker_symbol: Annotated[
|
154 |
+
str, "Ticker symbol of the stock (e.g., 'AAPL' for Apple)"
|
155 |
+
],
|
156 |
+
filing_date: Annotated[str | datetime, "filing date in 'YYYY-MM-DD' format"],
|
157 |
+
years: Annotated[int, "number of years to search from, default to 4"] = 4,
|
158 |
+
save_path: Annotated[str, "File path where the plot should be saved"] = None,
|
159 |
+
) -> str:
|
160 |
+
"""Plot the PE ratio and EPS performance of a company over the past n years."""
|
161 |
+
if isinstance(filing_date, str):
|
162 |
+
filing_date = datetime.strptime(filing_date, "%Y-%m-%d")
|
163 |
+
|
164 |
+
ss = YFinanceUtils.get_income_stmt(ticker_symbol)
|
165 |
+
eps = ss.loc["Diluted EPS", :]
|
166 |
+
|
167 |
+
# 获取过去5年的历史数据
|
168 |
+
# historical_data = self.stock.history(period="5y")
|
169 |
+
days = round((years + 1) * 365.25)
|
170 |
+
start = (filing_date - timedelta(days=days)).strftime("%Y-%m-%d")
|
171 |
+
end = filing_date.strftime("%Y-%m-%d")
|
172 |
+
historical_data = YFinanceUtils.get_stock_data(ticker_symbol, start, end)
|
173 |
+
|
174 |
+
# 指定的日期,并确保它们都是UTC时区的
|
175 |
+
dates = pd.to_datetime(eps.index[::-1], utc=True)
|
176 |
+
|
177 |
+
# 为了确保我们能够找到最接近的股市交易日,我们将转换日期并查找最接近的日期
|
178 |
+
results = {}
|
179 |
+
for date in dates:
|
180 |
+
# 如果指定日期不是交易日,使用bfill和ffill找到最近的交易日股价
|
181 |
+
if date not in historical_data.index:
|
182 |
+
close_price = historical_data.asof(date)
|
183 |
+
else:
|
184 |
+
close_price = historical_data.loc[date]
|
185 |
+
|
186 |
+
results[date] = close_price["Close"]
|
187 |
+
|
188 |
+
pe = [p / e for p, e in zip(results.values(), eps.values[::-1])]
|
189 |
+
dates = eps.index[::-1]
|
190 |
+
eps = eps.values[::-1]
|
191 |
+
|
192 |
+
info = YFinanceUtils.get_stock_info(ticker_symbol)
|
193 |
+
|
194 |
+
# 创建图形和轴对象
|
195 |
+
fig, ax1 = plt.subplots(figsize=(14, 7))
|
196 |
+
plt.rcParams.update({"font.size": 20}) # 调整为更大的字体大小
|
197 |
+
|
198 |
+
# 绘制市盈率
|
199 |
+
color = "tab:blue"
|
200 |
+
ax1.set_xlabel("Date")
|
201 |
+
ax1.set_ylabel("PE Ratio", color=color)
|
202 |
+
ax1.plot(dates, pe, color=color)
|
203 |
+
ax1.tick_params(axis="y", labelcolor=color)
|
204 |
+
ax1.grid(True)
|
205 |
+
|
206 |
+
# 创建与ax1共享x轴的第二个轴对象
|
207 |
+
ax2 = ax1.twinx()
|
208 |
+
color = "tab:red"
|
209 |
+
ax2.set_ylabel("EPS", color=color) # 第二个y轴的标签
|
210 |
+
ax2.plot(dates, eps, color=color)
|
211 |
+
ax2.tick_params(axis="y", labelcolor=color)
|
212 |
+
|
213 |
+
# 设置标题和x轴标签角度
|
214 |
+
plt.title(f'{info["shortName"]} PE Ratios and EPS Over the Past {years} Years')
|
215 |
+
plt.xticks(rotation=45)
|
216 |
+
|
217 |
+
# 设置x轴刻度标签
|
218 |
+
plt.xticks(dates, [d.strftime("%Y-%m") for d in dates])
|
219 |
+
|
220 |
+
plt.tight_layout()
|
221 |
+
# plt.show()
|
222 |
+
plot_path = (
|
223 |
+
f"{save_path}/pe_performance.png" if os.path.isdir(save_path) else save_path
|
224 |
+
)
|
225 |
+
plt.savefig(plot_path)
|
226 |
+
plt.close()
|
227 |
+
return f"pe performance chart saved to <img {plot_path}>"
|
228 |
+
|
229 |
+
|
230 |
+
if __name__ == "__main__":
|
231 |
+
# Example usage:
|
232 |
+
start_date = "2024-03-01"
|
233 |
+
end_date = "2024-04-01"
|
234 |
+
save_path = "AAPL_candlestick_chart.png"
|
235 |
+
MplFinanceUtils.plot_candlestick_chart("AAPL", start_date, end_date, save_path)
|
finrobot/functional/coding.py
ADDED
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from typing_extensions import Annotated
|
3 |
+
from IPython import get_ipython
|
4 |
+
|
5 |
+
default_path = "coding/"
|
6 |
+
|
7 |
+
|
8 |
+
class IPythonUtils:
|
9 |
+
|
10 |
+
def exec_python(cell: Annotated[str, "Valid Python cell to execute."]) -> str:
|
11 |
+
"""
|
12 |
+
run cell in ipython and return the execution result.
|
13 |
+
"""
|
14 |
+
ipython = get_ipython()
|
15 |
+
result = ipython.run_cell(cell)
|
16 |
+
log = str(result.result)
|
17 |
+
if result.error_before_exec is not None:
|
18 |
+
log += f"\n{result.error_before_exec}"
|
19 |
+
if result.error_in_exec is not None:
|
20 |
+
log += f"\n{result.error_in_exec}"
|
21 |
+
return log
|
22 |
+
|
23 |
+
def display_image(
|
24 |
+
image_path: Annotated[str, "Path to image file to display."]
|
25 |
+
) -> str:
|
26 |
+
"""
|
27 |
+
Display image in Jupyter Notebook.
|
28 |
+
"""
|
29 |
+
log = __class__.exec_python(
|
30 |
+
f"from IPython.display import Image, display\n\ndisplay(Image(filename='{image_path}'))"
|
31 |
+
)
|
32 |
+
if not log:
|
33 |
+
return "Image displayed successfully"
|
34 |
+
else:
|
35 |
+
return log
|
36 |
+
|
37 |
+
|
38 |
+
class CodingUtils: # Borrowed from https://microsoft.github.io/autogen/docs/notebooks/agentchat_function_call_code_writing
|
39 |
+
|
40 |
+
def list_dir(directory: Annotated[str, "Directory to check."]) -> str:
|
41 |
+
"""
|
42 |
+
List files in choosen directory.
|
43 |
+
"""
|
44 |
+
files = os.listdir(default_path + directory)
|
45 |
+
return str(files)
|
46 |
+
|
47 |
+
def see_file(filename: Annotated[str, "Name and path of file to check."]) -> str:
|
48 |
+
"""
|
49 |
+
Check the contents of a chosen file.
|
50 |
+
"""
|
51 |
+
with open(default_path + filename, "r") as file:
|
52 |
+
lines = file.readlines()
|
53 |
+
formatted_lines = [f"{i+1}:{line}" for i, line in enumerate(lines)]
|
54 |
+
file_contents = "".join(formatted_lines)
|
55 |
+
|
56 |
+
return file_contents
|
57 |
+
|
58 |
+
def modify_code(
|
59 |
+
filename: Annotated[str, "Name and path of file to change."],
|
60 |
+
start_line: Annotated[int, "Start line number to replace with new code."],
|
61 |
+
end_line: Annotated[int, "End line number to replace with new code."],
|
62 |
+
new_code: Annotated[
|
63 |
+
str,
|
64 |
+
"New piece of code to replace old code with. Remember about providing indents.",
|
65 |
+
],
|
66 |
+
) -> str:
|
67 |
+
"""
|
68 |
+
Replace old piece of code with new one. Proper indentation is important.
|
69 |
+
"""
|
70 |
+
with open(default_path + filename, "r+") as file:
|
71 |
+
file_contents = file.readlines()
|
72 |
+
file_contents[start_line - 1 : end_line] = [new_code + "\n"]
|
73 |
+
file.seek(0)
|
74 |
+
file.truncate()
|
75 |
+
file.write("".join(file_contents))
|
76 |
+
return "Code modified"
|
77 |
+
|
78 |
+
def create_file_with_code(
|
79 |
+
filename: Annotated[str, "Name and path of file to create."],
|
80 |
+
code: Annotated[str, "Code to write in the file."],
|
81 |
+
) -> str:
|
82 |
+
"""
|
83 |
+
Create a new file with provided code.
|
84 |
+
"""
|
85 |
+
directory = os.path.dirname(default_path + filename)
|
86 |
+
os.makedirs(directory, exist_ok=True)
|
87 |
+
with open(default_path + filename, "w") as file:
|
88 |
+
file.write(code)
|
89 |
+
return "File created successfully"
|
finrobot/functional/quantitative.py
ADDED
@@ -0,0 +1,184 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import json
|
3 |
+
import importlib
|
4 |
+
import yfinance as yf
|
5 |
+
import backtrader as bt
|
6 |
+
from backtrader.strategies import SMA_CrossOver
|
7 |
+
from typing import Annotated, List, Tuple
|
8 |
+
from matplotlib import pyplot as plt
|
9 |
+
from pprint import pformat
|
10 |
+
from IPython import get_ipython
|
11 |
+
|
12 |
+
|
13 |
+
class DeployedCapitalAnalyzer(bt.Analyzer):
|
14 |
+
def start(self):
|
15 |
+
self.deployed_capital = []
|
16 |
+
self.initial_cash = self.strategy.broker.get_cash() # Initial cash in account
|
17 |
+
|
18 |
+
def notify_order(self, order):
|
19 |
+
if order.status in [order.Completed]:
|
20 |
+
if order.isbuy():
|
21 |
+
self.deployed_capital.append(order.executed.price * order.executed.size)
|
22 |
+
elif order.issell():
|
23 |
+
self.deployed_capital.append(order.executed.price * order.executed.size)
|
24 |
+
|
25 |
+
def stop(self):
|
26 |
+
total_deployed = sum(self.deployed_capital)
|
27 |
+
final_cash = self.strategy.broker.get_value()
|
28 |
+
net_profit = final_cash - self.initial_cash
|
29 |
+
if total_deployed > 0:
|
30 |
+
self.retn = net_profit / total_deployed
|
31 |
+
else:
|
32 |
+
self.retn = 0
|
33 |
+
|
34 |
+
def get_analysis(self):
|
35 |
+
return {"return_on_deployed_capital": self.retn}
|
36 |
+
|
37 |
+
|
38 |
+
class BackTraderUtils:
|
39 |
+
|
40 |
+
def back_test(
|
41 |
+
ticker_symbol: Annotated[
|
42 |
+
str, "Ticker symbol of the stock (e.g., 'AAPL' for Apple)"
|
43 |
+
],
|
44 |
+
start_date: Annotated[
|
45 |
+
str, "Start date of the historical data in 'YYYY-MM-DD' format"
|
46 |
+
],
|
47 |
+
end_date: Annotated[
|
48 |
+
str, "End date of the historical data in 'YYYY-MM-DD' format"
|
49 |
+
],
|
50 |
+
strategy: Annotated[
|
51 |
+
str,
|
52 |
+
"BackTrader Strategy class to be backtested. Can be pre-defined or custom. Pre-defined options: 'SMA_CrossOver'. If custom, provide module path and class name as a string like 'my_module:TestStrategy'.",
|
53 |
+
],
|
54 |
+
strategy_params: Annotated[
|
55 |
+
str,
|
56 |
+
"Additional parameters to be passed to the strategy class formatted as json string. E.g. {'fast': 10, 'slow': 30} for SMACross.",
|
57 |
+
] = "",
|
58 |
+
sizer: Annotated[
|
59 |
+
int | str | None,
|
60 |
+
"Sizer used for backtesting. Can be a fixed number or a custom Sizer class. If input is integer, a corresponding fixed sizer will be applied. If custom, provide module path and class name as a string like 'my_module:TestSizer'.",
|
61 |
+
] = None,
|
62 |
+
sizer_params: Annotated[
|
63 |
+
str,
|
64 |
+
"Additional parameters to be passed to the sizer class formatted as json string.",
|
65 |
+
] = "",
|
66 |
+
indicator: Annotated[
|
67 |
+
str | None,
|
68 |
+
"Custom indicator class added to strategy. Provide module path and class name as a string like 'my_module:TestIndicator'.",
|
69 |
+
] = None,
|
70 |
+
indicator_params: Annotated[
|
71 |
+
str,
|
72 |
+
"Additional parameters to be passed to the indicator class formatted as json string.",
|
73 |
+
] = "",
|
74 |
+
cash: Annotated[
|
75 |
+
float, "Initial cash amount for the backtest. Default to 10000.0"
|
76 |
+
] = 10000.0,
|
77 |
+
save_fig: Annotated[
|
78 |
+
str | None, "Path to save the plot of backtest results. Default to None."
|
79 |
+
] = None,
|
80 |
+
) -> str:
|
81 |
+
"""
|
82 |
+
Use the Backtrader library to backtest a trading strategy on historical stock data.
|
83 |
+
"""
|
84 |
+
cerebro = bt.Cerebro()
|
85 |
+
|
86 |
+
if strategy == "SMA_CrossOver":
|
87 |
+
strategy_class = SMA_CrossOver
|
88 |
+
else:
|
89 |
+
assert (
|
90 |
+
":" in strategy
|
91 |
+
), "Custom strategy should be module path and class name separated by a colon."
|
92 |
+
module_path, class_name = strategy.split(":")
|
93 |
+
module = importlib.import_module(module_path)
|
94 |
+
strategy_class = getattr(module, class_name)
|
95 |
+
|
96 |
+
strategy_params = json.loads(strategy_params) if strategy_params else {}
|
97 |
+
cerebro.addstrategy(strategy_class, **strategy_params)
|
98 |
+
|
99 |
+
# Create a data feed
|
100 |
+
data = bt.feeds.PandasData(
|
101 |
+
dataname=yf.download(ticker_symbol, start_date, end_date, auto_adjust=True)
|
102 |
+
)
|
103 |
+
cerebro.adddata(data) # Add the data feed
|
104 |
+
# Set our desired cash start
|
105 |
+
cerebro.broker.setcash(cash)
|
106 |
+
|
107 |
+
# Set the size of the trades
|
108 |
+
if sizer is not None:
|
109 |
+
if isinstance(sizer, int):
|
110 |
+
cerebro.addsizer(bt.sizers.FixedSize, stake=sizer)
|
111 |
+
else:
|
112 |
+
assert (
|
113 |
+
":" in sizer
|
114 |
+
), "Custom sizer should be module path and class name separated by a colon."
|
115 |
+
module_path, class_name = sizer.split(":")
|
116 |
+
module = importlib.import_module(module_path)
|
117 |
+
sizer_class = getattr(module, class_name)
|
118 |
+
sizer_params = json.loads(sizer_params) if sizer_params else {}
|
119 |
+
cerebro.addsizer(sizer_class, **sizer_params)
|
120 |
+
|
121 |
+
# Set additional indicator
|
122 |
+
if indicator is not None:
|
123 |
+
assert (
|
124 |
+
":" in indicator
|
125 |
+
), "Custom indicator should be module path and class name separated by a colon."
|
126 |
+
module_path, class_name = indicator.split(":")
|
127 |
+
module = importlib.import_module(module_path)
|
128 |
+
indicator_class = getattr(module, class_name)
|
129 |
+
indicator_params = json.loads(indicator_params) if indicator_params else {}
|
130 |
+
cerebro.addindicator(indicator_class, **indicator_params)
|
131 |
+
|
132 |
+
# Attach analyzers
|
133 |
+
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name="sharpe_ratio")
|
134 |
+
cerebro.addanalyzer(bt.analyzers.DrawDown, _name="draw_down")
|
135 |
+
cerebro.addanalyzer(bt.analyzers.Returns, _name="returns")
|
136 |
+
cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name="trade_analyzer")
|
137 |
+
# cerebro.addanalyzer(DeployedCapitalAnalyzer, _name="deployed_capital")
|
138 |
+
|
139 |
+
stats_dict = {"Starting Portfolio Value:": cerebro.broker.getvalue()}
|
140 |
+
|
141 |
+
results = cerebro.run() # run it all
|
142 |
+
first_strategy = results[0]
|
143 |
+
|
144 |
+
# Access analysis results
|
145 |
+
stats_dict["Final Portfolio Value"] = cerebro.broker.getvalue()
|
146 |
+
# stats_dict["Deployed Capital"] = pformat(
|
147 |
+
# first_strategy.analyzers.deployed_capital.get_analysis(), indent=4
|
148 |
+
# )
|
149 |
+
stats_dict["Sharpe Ratio"] = (
|
150 |
+
first_strategy.analyzers.sharpe_ratio.get_analysis()
|
151 |
+
)
|
152 |
+
stats_dict["Drawdown"] = first_strategy.analyzers.draw_down.get_analysis()
|
153 |
+
stats_dict["Returns"] = first_strategy.analyzers.returns.get_analysis()
|
154 |
+
stats_dict["Trade Analysis"] = (
|
155 |
+
first_strategy.analyzers.trade_analyzer.get_analysis()
|
156 |
+
)
|
157 |
+
|
158 |
+
if save_fig:
|
159 |
+
directory = os.path.dirname(save_fig)
|
160 |
+
if directory:
|
161 |
+
os.makedirs(directory, exist_ok=True)
|
162 |
+
plt.figure(figsize=(12, 8))
|
163 |
+
cerebro.plot()
|
164 |
+
plt.savefig(save_fig)
|
165 |
+
plt.close()
|
166 |
+
|
167 |
+
return "Back Test Finished. Results: \n" + pformat(stats_dict, indent=2)
|
168 |
+
|
169 |
+
|
170 |
+
if __name__ == "__main__":
|
171 |
+
# Example usage:
|
172 |
+
start_date = "2011-01-01"
|
173 |
+
end_date = "2012-12-31"
|
174 |
+
ticker = "MSFT"
|
175 |
+
# BackTraderUtils.back_test(
|
176 |
+
# ticker, start_date, end_date, "SMA_CrossOver", {"fast": 10, "slow": 30}
|
177 |
+
# )
|
178 |
+
BackTraderUtils.back_test(
|
179 |
+
ticker,
|
180 |
+
start_date,
|
181 |
+
end_date,
|
182 |
+
"test_module:TestStrategy",
|
183 |
+
{"exitbars": 5},
|
184 |
+
)
|
finrobot/functional/reportlab.py
ADDED
@@ -0,0 +1,391 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import traceback
|
3 |
+
from reportlab.lib import colors
|
4 |
+
from reportlab.lib import pagesizes
|
5 |
+
from reportlab.platypus import (
|
6 |
+
SimpleDocTemplate,
|
7 |
+
Frame,
|
8 |
+
Paragraph,
|
9 |
+
Image,
|
10 |
+
PageTemplate,
|
11 |
+
FrameBreak,
|
12 |
+
Spacer,
|
13 |
+
Table,
|
14 |
+
TableStyle,
|
15 |
+
NextPageTemplate,
|
16 |
+
PageBreak,
|
17 |
+
)
|
18 |
+
from reportlab.lib.units import inch
|
19 |
+
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
20 |
+
from reportlab.lib.enums import TA_CENTER, TA_JUSTIFY, TA_LEFT
|
21 |
+
|
22 |
+
from ..data_source import FMPUtils, YFinanceUtils
|
23 |
+
from .analyzer import ReportAnalysisUtils
|
24 |
+
from typing import Annotated
|
25 |
+
|
26 |
+
|
27 |
+
class ReportLabUtils:
|
28 |
+
|
29 |
+
def build_annual_report(
|
30 |
+
ticker_symbol: Annotated[str, "ticker symbol"],
|
31 |
+
save_path: Annotated[str, "path to save the annual report pdf"],
|
32 |
+
income_summarization: Annotated[
|
33 |
+
str,
|
34 |
+
"a paragraph of text: the company's income summarization from its financial report",
|
35 |
+
],
|
36 |
+
business_highlights: Annotated[
|
37 |
+
str,
|
38 |
+
"a paragraph of text: the company's business highlights from its financial report",
|
39 |
+
],
|
40 |
+
company_description: Annotated[
|
41 |
+
str,
|
42 |
+
"a paragraph of text: the company's description and current situation from its financial report",
|
43 |
+
],
|
44 |
+
risk_assessment: Annotated[
|
45 |
+
str,
|
46 |
+
"a paragraph of text: the company's risk assessment from its financial report",
|
47 |
+
],
|
48 |
+
share_performance_image_path: Annotated[
|
49 |
+
str, "path to the share performance image"
|
50 |
+
],
|
51 |
+
pe_eps_performance_image_path: Annotated[
|
52 |
+
str, "path to the PE and EPS performance image"
|
53 |
+
],
|
54 |
+
filing_date: Annotated[str, "filing date of the analyzed financial report"],
|
55 |
+
) -> str:
|
56 |
+
"""
|
57 |
+
Aggregate a company's income summarization, business highlights, company description,
|
58 |
+
risk assessment and share performance, PE & EPS performance charts all into a PDF report.
|
59 |
+
"""
|
60 |
+
try:
|
61 |
+
# 2. 创建PDF并插入图像
|
62 |
+
# 页面设置
|
63 |
+
page_width, page_height = pagesizes.A4
|
64 |
+
left_column_width = page_width * 2 / 3
|
65 |
+
right_column_width = page_width - left_column_width
|
66 |
+
margin = 4
|
67 |
+
|
68 |
+
# 创建PDF文档路径
|
69 |
+
pdf_path = (
|
70 |
+
os.path.join(save_path, f"{ticker_symbol}_report.pdf")
|
71 |
+
if os.path.isdir(save_path)
|
72 |
+
else save_path
|
73 |
+
)
|
74 |
+
os.makedirs(os.path.dirname(pdf_path), exist_ok=True)
|
75 |
+
doc = SimpleDocTemplate(pdf_path, pagesize=pagesizes.A4)
|
76 |
+
|
77 |
+
# 定义两个栏位的Frame
|
78 |
+
frame_left = Frame(
|
79 |
+
margin,
|
80 |
+
margin,
|
81 |
+
left_column_width - margin * 2,
|
82 |
+
page_height - margin * 2,
|
83 |
+
id="left",
|
84 |
+
)
|
85 |
+
frame_right = Frame(
|
86 |
+
left_column_width,
|
87 |
+
margin,
|
88 |
+
right_column_width - margin * 2,
|
89 |
+
page_height - margin * 2,
|
90 |
+
id="right",
|
91 |
+
)
|
92 |
+
|
93 |
+
# single_frame = Frame(margin, margin, page_width-margin*2, page_height-margin*2, id='single')
|
94 |
+
# single_column_layout = PageTemplate(id='OneCol', frames=[single_frame])
|
95 |
+
|
96 |
+
left_column_width_p2 = (page_width - margin * 3) // 2
|
97 |
+
right_column_width_p2 = left_column_width_p2
|
98 |
+
frame_left_p2 = Frame(
|
99 |
+
margin,
|
100 |
+
margin,
|
101 |
+
left_column_width_p2 - margin * 2,
|
102 |
+
page_height - margin * 2,
|
103 |
+
id="left",
|
104 |
+
)
|
105 |
+
frame_right_p2 = Frame(
|
106 |
+
left_column_width_p2,
|
107 |
+
margin,
|
108 |
+
right_column_width_p2 - margin * 2,
|
109 |
+
page_height - margin * 2,
|
110 |
+
id="right",
|
111 |
+
)
|
112 |
+
|
113 |
+
# 创建PageTemplate,并添加到文档
|
114 |
+
page_template = PageTemplate(
|
115 |
+
id="TwoColumns", frames=[frame_left, frame_right]
|
116 |
+
)
|
117 |
+
page_template_p2 = PageTemplate(
|
118 |
+
id="TwoColumns_p2", frames=[frame_left_p2, frame_right_p2]
|
119 |
+
)
|
120 |
+
|
121 |
+
# Define single column Frame
|
122 |
+
single_frame = Frame(
|
123 |
+
margin,
|
124 |
+
margin,
|
125 |
+
page_width - 2 * margin,
|
126 |
+
page_height - 2 * margin,
|
127 |
+
id="single",
|
128 |
+
)
|
129 |
+
|
130 |
+
# Create a PageTemplate with a single column
|
131 |
+
single_column_layout = PageTemplate(id="OneCol", frames=[single_frame])
|
132 |
+
|
133 |
+
doc.addPageTemplates(
|
134 |
+
[page_template, single_column_layout, page_template_p2]
|
135 |
+
)
|
136 |
+
|
137 |
+
styles = getSampleStyleSheet()
|
138 |
+
|
139 |
+
# 自定义样式
|
140 |
+
custom_style = ParagraphStyle(
|
141 |
+
name="Custom",
|
142 |
+
parent=styles["Normal"],
|
143 |
+
fontName="Helvetica",
|
144 |
+
fontSize=10,
|
145 |
+
# leading=15,
|
146 |
+
alignment=TA_JUSTIFY,
|
147 |
+
)
|
148 |
+
|
149 |
+
title_style = ParagraphStyle(
|
150 |
+
name="TitleCustom",
|
151 |
+
parent=styles["Title"],
|
152 |
+
fontName="Helvetica-Bold",
|
153 |
+
fontSize=16,
|
154 |
+
leading=20,
|
155 |
+
alignment=TA_LEFT,
|
156 |
+
spaceAfter=10,
|
157 |
+
)
|
158 |
+
|
159 |
+
subtitle_style = ParagraphStyle(
|
160 |
+
name="Subtitle",
|
161 |
+
parent=styles["Heading2"],
|
162 |
+
fontName="Helvetica-Bold",
|
163 |
+
fontSize=14,
|
164 |
+
leading=12,
|
165 |
+
alignment=TA_LEFT,
|
166 |
+
spaceAfter=6,
|
167 |
+
)
|
168 |
+
|
169 |
+
table_style2 = TableStyle(
|
170 |
+
[
|
171 |
+
("BACKGROUND", (0, 0), (-1, -1), colors.white),
|
172 |
+
("BACKGROUND", (0, 0), (-1, 0), colors.white),
|
173 |
+
("FONT", (0, 0), (-1, -1), "Helvetica", 7),
|
174 |
+
("FONT", (0, 0), (-1, 0), "Helvetica-Bold", 14),
|
175 |
+
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
|
176 |
+
# 所有单元格左对齐
|
177 |
+
("ALIGN", (0, 0), (-1, -1), "LEFT"),
|
178 |
+
# 标题栏下方添加横线
|
179 |
+
("LINEBELOW", (0, 0), (-1, 0), 2, colors.black),
|
180 |
+
# 表格最下方添加横线
|
181 |
+
("LINEBELOW", (0, -1), (-1, -1), 2, colors.black),
|
182 |
+
]
|
183 |
+
)
|
184 |
+
|
185 |
+
name = YFinanceUtils.get_stock_info(ticker_symbol)["shortName"]
|
186 |
+
|
187 |
+
# 准备左栏和右栏内容
|
188 |
+
content = []
|
189 |
+
# 标题
|
190 |
+
content.append(
|
191 |
+
Paragraph(
|
192 |
+
f"Equity Research Report: {name}",
|
193 |
+
title_style,
|
194 |
+
)
|
195 |
+
)
|
196 |
+
|
197 |
+
# 子标题
|
198 |
+
content.append(Paragraph("Income Summarization", subtitle_style))
|
199 |
+
content.append(Paragraph(income_summarization, custom_style))
|
200 |
+
|
201 |
+
content.append(Paragraph("Business Highlights", subtitle_style))
|
202 |
+
content.append(Paragraph(business_highlights, custom_style))
|
203 |
+
|
204 |
+
content.append(Paragraph("Company Situation", subtitle_style))
|
205 |
+
content.append(Paragraph(company_description, custom_style))
|
206 |
+
|
207 |
+
content.append(Paragraph("Risk Assessment", subtitle_style))
|
208 |
+
content.append(Paragraph(risk_assessment, custom_style))
|
209 |
+
|
210 |
+
# content.append(Paragraph("Summarization", subtitle_style))
|
211 |
+
df = FMPUtils.get_financial_metrics(ticker_symbol, years=5)
|
212 |
+
df.reset_index(inplace=True)
|
213 |
+
currency = YFinanceUtils.get_stock_info(ticker_symbol)["currency"]
|
214 |
+
df.rename(columns={"index": f"FY ({currency} mn)"}, inplace=True)
|
215 |
+
table_data = [["Financial Metrics"]]
|
216 |
+
table_data += [df.columns.to_list()] + df.values.tolist()
|
217 |
+
|
218 |
+
col_widths = [(left_column_width - margin * 4) / df.shape[1]] * df.shape[1]
|
219 |
+
table = Table(table_data, colWidths=col_widths)
|
220 |
+
table.setStyle(table_style2)
|
221 |
+
content.append(table)
|
222 |
+
|
223 |
+
content.append(FrameBreak()) # 用于从左栏跳到右栏
|
224 |
+
|
225 |
+
table_style = TableStyle(
|
226 |
+
[
|
227 |
+
("BACKGROUND", (0, 0), (-1, -1), colors.white),
|
228 |
+
("BACKGROUND", (0, 0), (-1, 0), colors.white),
|
229 |
+
("FONT", (0, 0), (-1, -1), "Helvetica", 8),
|
230 |
+
("FONT", (0, 0), (-1, 0), "Helvetica-Bold", 12),
|
231 |
+
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
|
232 |
+
# 第一列左对齐
|
233 |
+
("ALIGN", (0, 1), (0, -1), "LEFT"),
|
234 |
+
# 第二列右对齐
|
235 |
+
("ALIGN", (1, 1), (1, -1), "RIGHT"),
|
236 |
+
# 标题栏下方添加横线
|
237 |
+
("LINEBELOW", (0, 0), (-1, 0), 2, colors.black),
|
238 |
+
]
|
239 |
+
)
|
240 |
+
full_length = right_column_width - 2 * margin
|
241 |
+
|
242 |
+
data = [
|
243 |
+
["FinRobot"],
|
244 |
+
["https://ai4finance.org/"],
|
245 |
+
["https://github.com/AI4Finance-Foundation/FinRobot"],
|
246 |
+
[f"Report date: {filing_date}"],
|
247 |
+
]
|
248 |
+
col_widths = [full_length]
|
249 |
+
table = Table(data, colWidths=col_widths)
|
250 |
+
table.setStyle(table_style)
|
251 |
+
content.append(table)
|
252 |
+
|
253 |
+
# content.append(Paragraph("", custom_style))
|
254 |
+
content.append(Spacer(1, 0.15 * inch))
|
255 |
+
key_data = ReportAnalysisUtils.get_key_data(ticker_symbol, filing_date)
|
256 |
+
# 表格数据
|
257 |
+
data = [["Key data", ""]]
|
258 |
+
data += [[k, v] for k, v in key_data.items()]
|
259 |
+
col_widths = [full_length // 3 * 2, full_length // 3]
|
260 |
+
table = Table(data, colWidths=col_widths)
|
261 |
+
table.setStyle(table_style)
|
262 |
+
content.append(table)
|
263 |
+
|
264 |
+
# 将Matplotlib图像添加到右栏
|
265 |
+
|
266 |
+
# 历史股价
|
267 |
+
data = [["Share Performance"]]
|
268 |
+
col_widths = [full_length]
|
269 |
+
table = Table(data, colWidths=col_widths)
|
270 |
+
table.setStyle(table_style)
|
271 |
+
content.append(table)
|
272 |
+
|
273 |
+
plot_path = share_performance_image_path
|
274 |
+
width = right_column_width
|
275 |
+
height = width // 2
|
276 |
+
content.append(Image(plot_path, width=width, height=height))
|
277 |
+
|
278 |
+
# 历史PE和EPS
|
279 |
+
data = [["PE & EPS"]]
|
280 |
+
col_widths = [full_length]
|
281 |
+
table = Table(data, colWidths=col_widths)
|
282 |
+
table.setStyle(table_style)
|
283 |
+
content.append(table)
|
284 |
+
|
285 |
+
plot_path = pe_eps_performance_image_path
|
286 |
+
width = right_column_width
|
287 |
+
height = width // 2
|
288 |
+
content.append(Image(plot_path, width=width, height=height))
|
289 |
+
|
290 |
+
# # 开始新的一页
|
291 |
+
# content.append(NextPageTemplate("OneCol"))
|
292 |
+
# content.append(PageBreak())
|
293 |
+
|
294 |
+
# def add_table(df, title):
|
295 |
+
# df = df.applymap(lambda x: "{:.2f}".format(x) if isinstance(x, float) else x)
|
296 |
+
# # df.columns = [col.strftime('%Y') for col in df.columns]
|
297 |
+
# # df.reset_index(inplace=True)
|
298 |
+
# # currency = ra.info['currency']
|
299 |
+
# df.rename(columns={"index": "segment"}, inplace=True)
|
300 |
+
# table_data = [[title]]
|
301 |
+
# table_data += [df.columns.to_list()] + df.values.tolist()
|
302 |
+
|
303 |
+
# table = Table(table_data)
|
304 |
+
# table.setStyle(table_style2)
|
305 |
+
# num_columns = len(df.columns)
|
306 |
+
|
307 |
+
# column_width = (page_width - 4 * margin) / (num_columns + 1)
|
308 |
+
# first_column_witdh = column_width * 2
|
309 |
+
# table._argW = [first_column_witdh] + [column_width] * (num_columns - 1)
|
310 |
+
|
311 |
+
# content.append(table)
|
312 |
+
# content.append(Spacer(1, 0.15 * inch))
|
313 |
+
|
314 |
+
# if os.path.exists(f"{ra.project_dir}/outer_resource/"):
|
315 |
+
# Revenue10Q = pd.read_csv(
|
316 |
+
# f"{ra.project_dir}/outer_resource/Revenue10Q.csv",
|
317 |
+
# )
|
318 |
+
# # del Revenue10K['FY2018']
|
319 |
+
# # del Revenue10K['FY2019']
|
320 |
+
# add_table(Revenue10Q, "Revenue")
|
321 |
+
|
322 |
+
# Ratio10Q = pd.read_csv(
|
323 |
+
# f"{ra.project_dir}/outer_resource/Ratio10Q.csv",
|
324 |
+
# )
|
325 |
+
# # del Ratio10K['FY2018']
|
326 |
+
# # del Ratio10K['FY2019']
|
327 |
+
# add_table(Ratio10Q, "Ratio")
|
328 |
+
|
329 |
+
# Yoy10Q = pd.read_csv(
|
330 |
+
# f"{ra.project_dir}/outer_resource/Yoy10Q.csv",
|
331 |
+
# )
|
332 |
+
# # del Yoy10K['FY2018']
|
333 |
+
# # del Yoy10K['FY2019']
|
334 |
+
# add_table(Yoy10Q, "Yoy")
|
335 |
+
|
336 |
+
# plot_path = os.path.join(f"{ra.project_dir}/outer_resource/", "segment.png")
|
337 |
+
# width = page_width - 2 * margin
|
338 |
+
# height = width * 3 // 5
|
339 |
+
# content.append(Image(plot_path, width=width, height=height))
|
340 |
+
|
341 |
+
# # 第二页及之后内容,使用单栏布局
|
342 |
+
# df = ra.get_income_stmt()
|
343 |
+
# df = df[df.columns[:3]]
|
344 |
+
# def convert_if_money(value):
|
345 |
+
# if np.abs(value) >= 1000000:
|
346 |
+
# return value / 1000000
|
347 |
+
# else:
|
348 |
+
# return value
|
349 |
+
|
350 |
+
# # 应用转换函数到DataFrame的每列
|
351 |
+
# df = df.applymap(convert_if_money)
|
352 |
+
|
353 |
+
# df.columns = [col.strftime('%Y') for col in df.columns]
|
354 |
+
# df.reset_index(inplace=True)
|
355 |
+
# currency = ra.info['currency']
|
356 |
+
# df.rename(columns={'index': f'FY ({currency} mn)'}, inplace=True) # 可选:重命名索引列为“序号”
|
357 |
+
# table_data = [["Income Statement"]]
|
358 |
+
# table_data += [df.columns.to_list()] + df.values.tolist()
|
359 |
+
|
360 |
+
# table = Table(table_data)
|
361 |
+
# table.setStyle(table_style2)
|
362 |
+
# content.append(table)
|
363 |
+
|
364 |
+
# content.append(FrameBreak()) # 用于从左栏跳到右栏
|
365 |
+
|
366 |
+
# df = ra.get_cash_flow()
|
367 |
+
# df = df[df.columns[:3]]
|
368 |
+
|
369 |
+
# df = df.applymap(convert_if_money)
|
370 |
+
|
371 |
+
# df.columns = [col.strftime('%Y') for col in df.columns]
|
372 |
+
# df.reset_index(inplace=True)
|
373 |
+
# currency = ra.info['currency']
|
374 |
+
# df.rename(columns={'index': f'FY ({currency} mn)'}, inplace=True) # 可选:重命名索引列为“序号”
|
375 |
+
# table_data = [["Cash Flow Sheet"]]
|
376 |
+
# table_data += [df.columns.to_list()] + df.values.tolist()
|
377 |
+
|
378 |
+
# table = Table(table_data)
|
379 |
+
# table.setStyle(table_style2)
|
380 |
+
# content.append(table)
|
381 |
+
# # content.append(Paragraph('This is a single column on the second page', custom_style))
|
382 |
+
# # content.append(Spacer(1, 0.2*inch))
|
383 |
+
# # content.append(Paragraph('More content in the single column.', custom_style))
|
384 |
+
|
385 |
+
# 构建PDF文档
|
386 |
+
doc.build(content)
|
387 |
+
|
388 |
+
return "Annual report generated successfully."
|
389 |
+
|
390 |
+
except Exception:
|
391 |
+
return traceback.format_exc()
|
finrobot/functional/text.py
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import Annotated
|
2 |
+
|
3 |
+
class TextUtils:
|
4 |
+
|
5 |
+
def check_text_length(
|
6 |
+
text: Annotated[str, "text to check"],
|
7 |
+
min_length: Annotated[int, "minimum length of the text, default to 0"] = 0,
|
8 |
+
max_length: Annotated[int, "maximum length of the text, default to 100000"] = 100000,
|
9 |
+
) -> str:
|
10 |
+
"""
|
11 |
+
Check if the length of the text is exceeds than the maximum length.
|
12 |
+
"""
|
13 |
+
length = len(text.split())
|
14 |
+
if length > max_length:
|
15 |
+
return f"Text length {length} exceeds the maximum length of {max_length}."
|
16 |
+
elif length < min_length:
|
17 |
+
return f"Text length {length} is less than the minimum length of {min_length}."
|
18 |
+
else:
|
19 |
+
return f"Text length {length} is within the expected range."
|
finrobot/toolkits.py
ADDED
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from autogen import register_function, ConversableAgent
|
2 |
+
from .data_source import *
|
3 |
+
from .functional.coding import CodingUtils
|
4 |
+
|
5 |
+
from typing import List, Callable
|
6 |
+
from functools import wraps
|
7 |
+
from pandas import DataFrame
|
8 |
+
|
9 |
+
|
10 |
+
def stringify_output(func):
|
11 |
+
@wraps(func)
|
12 |
+
def wrapper(*args, **kwargs):
|
13 |
+
result = func(*args, **kwargs)
|
14 |
+
if isinstance(result, DataFrame):
|
15 |
+
return result.to_string()
|
16 |
+
else:
|
17 |
+
return str(result)
|
18 |
+
|
19 |
+
return wrapper
|
20 |
+
|
21 |
+
|
22 |
+
def register_toolkits(
|
23 |
+
config: List[dict | Callable | type],
|
24 |
+
caller: ConversableAgent,
|
25 |
+
executor: ConversableAgent,
|
26 |
+
**kwargs
|
27 |
+
):
|
28 |
+
"""Register tools from a configuration list."""
|
29 |
+
|
30 |
+
for tool in config:
|
31 |
+
|
32 |
+
if isinstance(tool, type):
|
33 |
+
register_tookits_from_cls(caller, executor, tool, **kwargs)
|
34 |
+
continue
|
35 |
+
|
36 |
+
tool_dict = {"function": tool} if callable(tool) else tool
|
37 |
+
if "function" not in tool_dict or not callable(tool_dict["function"]):
|
38 |
+
raise ValueError(
|
39 |
+
"Function not found in tool configuration or not callable."
|
40 |
+
)
|
41 |
+
|
42 |
+
tool_function = tool_dict["function"]
|
43 |
+
name = tool_dict.get("name", tool_function.__name__)
|
44 |
+
description = tool_dict.get("description", tool_function.__doc__)
|
45 |
+
register_function(
|
46 |
+
stringify_output(tool_function),
|
47 |
+
caller=caller,
|
48 |
+
executor=executor,
|
49 |
+
name=name,
|
50 |
+
description=description,
|
51 |
+
)
|
52 |
+
|
53 |
+
|
54 |
+
def register_code_writing(caller: ConversableAgent, executor: ConversableAgent):
|
55 |
+
"""Register code writing tools."""
|
56 |
+
|
57 |
+
register_toolkits(
|
58 |
+
[
|
59 |
+
{
|
60 |
+
"function": CodingUtils.list_dir,
|
61 |
+
"name": "list_files",
|
62 |
+
"description": "List files in a directory.",
|
63 |
+
},
|
64 |
+
{
|
65 |
+
"function": CodingUtils.see_file,
|
66 |
+
"name": "see_file",
|
67 |
+
"description": "Check the contents of a chosen file.",
|
68 |
+
},
|
69 |
+
{
|
70 |
+
"function": CodingUtils.modify_code,
|
71 |
+
"name": "modify_code",
|
72 |
+
"description": "Replace old piece of code with new one.",
|
73 |
+
},
|
74 |
+
{
|
75 |
+
"function": CodingUtils.create_file_with_code,
|
76 |
+
"name": "create_file_with_code",
|
77 |
+
"description": "Create a new file with provided code.",
|
78 |
+
},
|
79 |
+
],
|
80 |
+
caller,
|
81 |
+
executor,
|
82 |
+
)
|
83 |
+
|
84 |
+
|
85 |
+
def register_tookits_from_cls(
|
86 |
+
caller: ConversableAgent,
|
87 |
+
executor: ConversableAgent,
|
88 |
+
cls: type,
|
89 |
+
include_private: bool = False,
|
90 |
+
):
|
91 |
+
"""Register all methods of a class as tools."""
|
92 |
+
if include_private:
|
93 |
+
funcs = [
|
94 |
+
func
|
95 |
+
for func in dir(cls)
|
96 |
+
if callable(getattr(cls, func)) and not func.startswith("__")
|
97 |
+
]
|
98 |
+
else:
|
99 |
+
funcs = [
|
100 |
+
func
|
101 |
+
for func in dir(cls)
|
102 |
+
if callable(getattr(cls, func))
|
103 |
+
and not func.startswith("__")
|
104 |
+
and not func.startswith("_")
|
105 |
+
]
|
106 |
+
register_toolkits([getattr(cls, func) for func in funcs], caller, executor)
|
finrobot/utils.py
ADDED
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import json
|
3 |
+
import pandas as pd
|
4 |
+
from datetime import date, timedelta, datetime
|
5 |
+
from typing import Annotated
|
6 |
+
|
7 |
+
|
8 |
+
# Define custom annotated types
|
9 |
+
# VerboseType = Annotated[bool, "Whether to print data to console. Default to True."]
|
10 |
+
SavePathType = Annotated[str, "File path to save data. If None, data is not saved."]
|
11 |
+
|
12 |
+
|
13 |
+
# def process_output(data: pd.DataFrame, tag: str, verbose: VerboseType = True, save_path: SavePathType = None) -> None:
|
14 |
+
# if verbose:
|
15 |
+
# print(data.to_string())
|
16 |
+
# if save_path:
|
17 |
+
# data.to_csv(save_path)
|
18 |
+
# print(f"{tag} saved to {save_path}")
|
19 |
+
|
20 |
+
|
21 |
+
def save_output(data: pd.DataFrame, tag: str, save_path: SavePathType = None) -> None:
|
22 |
+
if save_path:
|
23 |
+
data.to_csv(save_path)
|
24 |
+
print(f"{tag} saved to {save_path}")
|
25 |
+
|
26 |
+
|
27 |
+
def get_current_date():
|
28 |
+
return date.today().strftime("%Y-%m-%d")
|
29 |
+
|
30 |
+
|
31 |
+
def register_keys_from_json(file_path):
|
32 |
+
with open(file_path, "r") as f:
|
33 |
+
keys = json.load(f)
|
34 |
+
for key, value in keys.items():
|
35 |
+
os.environ[key] = value
|
36 |
+
|
37 |
+
|
38 |
+
def decorate_all_methods(decorator):
|
39 |
+
def class_decorator(cls):
|
40 |
+
for attr_name, attr_value in cls.__dict__.items():
|
41 |
+
if callable(attr_value):
|
42 |
+
setattr(cls, attr_name, decorator(attr_value))
|
43 |
+
return cls
|
44 |
+
|
45 |
+
return class_decorator
|
46 |
+
|
47 |
+
|
48 |
+
def get_next_weekday(date):
|
49 |
+
|
50 |
+
if not isinstance(date, datetime):
|
51 |
+
date = datetime.strptime(date, "%Y-%m-%d")
|
52 |
+
|
53 |
+
if date.weekday() >= 5:
|
54 |
+
days_to_add = 7 - date.weekday()
|
55 |
+
next_weekday = date + timedelta(days=days_to_add)
|
56 |
+
return next_weekday
|
57 |
+
else:
|
58 |
+
return date
|
59 |
+
|
60 |
+
|
61 |
+
# def create_inner_assistant(
|
62 |
+
# name, system_message, llm_config, max_round=10,
|
63 |
+
# code_execution_config=None
|
64 |
+
# ):
|
65 |
+
|
66 |
+
# inner_assistant = autogen.AssistantAgent(
|
67 |
+
# name=name,
|
68 |
+
# system_message=system_message + "Reply TERMINATE when the task is done.",
|
69 |
+
# llm_config=llm_config,
|
70 |
+
# is_termination_msg=lambda x: x.get("content", "").find("TERMINATE") >= 0,
|
71 |
+
# )
|
72 |
+
# executor = autogen.UserProxyAgent(
|
73 |
+
# name=f"{name}-executor",
|
74 |
+
# human_input_mode="NEVER",
|
75 |
+
# code_execution_config=code_execution_config,
|
76 |
+
# default_auto_reply="",
|
77 |
+
# is_termination_msg=lambda x: x.get("content", "").find("TERMINATE") >= 0,
|
78 |
+
# )
|
79 |
+
# assistant.register_nested_chats(
|
80 |
+
# [{"recipient": assistant, "message": reflection_message, "summary_method": "last_msg", "max_turns": 1}],
|
81 |
+
# trigger=ConversableAgent
|
82 |
+
# )
|
83 |
+
# return manager
|
report/Microsoft_Annual_Report_2023.pdf
ADDED
The diff for this file is too large to render.
See raw diff
|
|
report/NVDA_report.pdf
ADDED
The diff for this file is too large to render.
See raw diff
|
|
requirements.txt
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
pyautogen>=0.2.19
|
2 |
+
ipython
|
3 |
+
huggingface_hub
|
4 |
+
matplotlib
|
5 |
+
python-dotenv
|
6 |
+
gradio
|
7 |
+
# financial libraries
|
8 |
+
finnhub-python
|
9 |
+
yfinance
|
10 |
+
mplfinance
|
11 |
+
backtrader
|
12 |
+
sec_api
|
13 |
+
|
14 |
+
# data handling
|
15 |
+
numpy
|
16 |
+
pandas
|
17 |
+
pyPDF2
|
18 |
+
reportlab
|
19 |
+
pyautogen[retrievechat]
|
20 |
+
|
21 |
+
# packaging
|
22 |
+
setuptools>=41.4.0
|
23 |
+
wheel>=0.33.6
|
24 |
+
|
25 |
+
# historical data
|
26 |
+
tushare
|
27 |
+
pandas_datareader
|
servercn.py
ADDED
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import json
|
3 |
+
import pandas as pd
|
4 |
+
from datetime import date
|
5 |
+
import gradio as gr
|
6 |
+
import autogen
|
7 |
+
from autogen.cache import Cache
|
8 |
+
from finrobot.utils import get_current_date
|
9 |
+
from finrobot.data_source import FinnHubUtils, YFinanceUtils
|
10 |
+
from dotenv import load_dotenv
|
11 |
+
|
12 |
+
# Load environment variables from .env file
|
13 |
+
load_dotenv()
|
14 |
+
|
15 |
+
# Utility functions
|
16 |
+
def save_output(data: pd.DataFrame, tag: str, save_path: str = None) -> None:
|
17 |
+
if save_path:
|
18 |
+
data.to_csv(save_path)
|
19 |
+
print(f"{tag} saved to {save_path}")
|
20 |
+
|
21 |
+
def get_current_date():
|
22 |
+
return date.today().strftime("%Y-%m-%d")
|
23 |
+
|
24 |
+
def register_keys():
|
25 |
+
keys = {
|
26 |
+
"FINNHUB_API_KEY": os.getenv("FINNHUB_API_KEY"),
|
27 |
+
"FMP_API_KEY": os.getenv("FMP_API_KEY"),
|
28 |
+
"SEC_API_KEY": os.getenv("SEC_API_KEY")
|
29 |
+
}
|
30 |
+
for key, value in keys.items():
|
31 |
+
if value:
|
32 |
+
os.environ[key] = value
|
33 |
+
|
34 |
+
def read_response_from_md(filename):
|
35 |
+
with open(filename, "r") as file:
|
36 |
+
content = file.read()
|
37 |
+
return content
|
38 |
+
|
39 |
+
def save_to_md(content, filename):
|
40 |
+
with open(filename, "w") as file: # Use write mode to overwrite the file
|
41 |
+
file.write(content + "\n")
|
42 |
+
print(f"Content saved to {filename}")
|
43 |
+
|
44 |
+
# Initialize LLM configuration
|
45 |
+
config_list = [
|
46 |
+
{
|
47 |
+
"model": "gpt-4o",
|
48 |
+
"api_key": os.getenv("OPENAI_API_KEY")
|
49 |
+
}
|
50 |
+
]
|
51 |
+
llm_config = {"config_list": config_list, "timeout": 120, "temperature": 0}
|
52 |
+
|
53 |
+
# Register FINNHUB API keys
|
54 |
+
register_keys()
|
55 |
+
|
56 |
+
# Define agents
|
57 |
+
analyst = autogen.AssistantAgent(
|
58 |
+
name="Market_Analyst",
|
59 |
+
system_message="As a Market Analyst, one must possess strong analytical and problem-solving abilities, collect necessary financial information and aggregate them based on client's requirement. For coding tasks, only use the functions you have been provided with. Reply TERMINATE when the task is done.",
|
60 |
+
llm_config=llm_config,
|
61 |
+
)
|
62 |
+
|
63 |
+
user_proxy = autogen.UserProxyAgent(
|
64 |
+
name="User_Proxy",
|
65 |
+
is_termination_msg=lambda x: x.get("content", "") and x.get("content", "").strip().endswith("TERMINATE"),
|
66 |
+
human_input_mode="NEVER",
|
67 |
+
max_consecutive_auto_reply=10,
|
68 |
+
code_execution_config={
|
69 |
+
"work_dir": "coding",
|
70 |
+
"use_docker": False,
|
71 |
+
},
|
72 |
+
)
|
73 |
+
|
74 |
+
# Register tools
|
75 |
+
from finrobot.toolkits import register_toolkits
|
76 |
+
|
77 |
+
tools = [
|
78 |
+
{
|
79 |
+
"function": FinnHubUtils.get_company_profile,
|
80 |
+
"name": "get_company_profile",
|
81 |
+
"description": "get a company's profile information"
|
82 |
+
},
|
83 |
+
{
|
84 |
+
"function": FinnHubUtils.get_company_news,
|
85 |
+
"name": "get_company_news",
|
86 |
+
"description": "retrieve market news related to designated company"
|
87 |
+
},
|
88 |
+
{
|
89 |
+
"function": FinnHubUtils.get_basic_financials,
|
90 |
+
"name": "get_financial_basics",
|
91 |
+
"description": "get latest financial basics for a designated company"
|
92 |
+
},
|
93 |
+
{
|
94 |
+
"function": YFinanceUtils.get_stock_data,
|
95 |
+
"name": "get_stock_data",
|
96 |
+
"description": "retrieve stock price data for designated ticker symbol"
|
97 |
+
}
|
98 |
+
]
|
99 |
+
register_toolkits(tools, analyst, user_proxy)
|
100 |
+
|
101 |
+
def save_response_to_json(response, filename):
|
102 |
+
response_dict = {
|
103 |
+
"chat_id": response.chat_id,
|
104 |
+
"chat_history": response.chat_history,
|
105 |
+
"summary": response.summary,
|
106 |
+
"cost": response.cost,
|
107 |
+
"human_input": response.human_input
|
108 |
+
}
|
109 |
+
with open(filename, "w") as file:
|
110 |
+
file.write(json.dumps(response_dict, indent=4))
|
111 |
+
print(f"Response saved to {filename}")
|
112 |
+
|
113 |
+
# Function to initiate chat and save response
|
114 |
+
def initiate_chat_and_save_response(analyst, user_proxy, company):
|
115 |
+
today_date = get_current_date()
|
116 |
+
json_filename = f"result_{company}_{today_date}.json"
|
117 |
+
md_filename = f"result_{company}_{today_date}.md"
|
118 |
+
|
119 |
+
# Check if MD file already exists
|
120 |
+
if os.path.exists(md_filename):
|
121 |
+
return read_response_from_md(md_filename)
|
122 |
+
|
123 |
+
with Cache.disk() as cache:
|
124 |
+
response = user_proxy.initiate_chat(
|
125 |
+
analyst,
|
126 |
+
message=f"Report in Chinese. Use all the tools provided to retrieve information available for {company} upon {get_current_date()}. Analyze the positive developments and potential concerns of {company} with 2-4 most important factors respectively and keep them concise. Most factors should be inferred from company related news. Then make a rough prediction (e.g. up/down by %) of the {company} stock price movement for next week. Provide a summary analysis to support your prediction.",
|
127 |
+
cache=cache,
|
128 |
+
)
|
129 |
+
|
130 |
+
save_response_to_json(response, json_filename)
|
131 |
+
return json.dumps(response.chat_history, indent=4)
|
132 |
+
|
133 |
+
def filter_user_content(chat_history):
|
134 |
+
# 解析 chat_history 为 JSON 对象
|
135 |
+
chat_history_dict = json.loads(chat_history)
|
136 |
+
# 查找用户需要的内容
|
137 |
+
for entry in chat_history_dict:
|
138 |
+
if entry['role'] == 'user' and "###" in entry['content']:
|
139 |
+
return entry['content']
|
140 |
+
return "No relevant content found."
|
141 |
+
|
142 |
+
# 使用更新的函数在 analyze_company 中
|
143 |
+
def analyze_company(company):
|
144 |
+
if company:
|
145 |
+
company = company.upper()
|
146 |
+
today_date = get_current_date()
|
147 |
+
md_filename = f"result_{company}_{today_date}.md"
|
148 |
+
|
149 |
+
# Check if MD file already exists
|
150 |
+
if os.path.exists(md_filename):
|
151 |
+
return read_response_from_md(md_filename)
|
152 |
+
|
153 |
+
content = initiate_chat_and_save_response(analyst, user_proxy, company)
|
154 |
+
# 筛选有效的用户内容
|
155 |
+
filtered_content = filter_user_content(content)
|
156 |
+
save_to_md(filtered_content, md_filename) # 只保存筛选后的内容
|
157 |
+
return filtered_content
|
158 |
+
|
159 |
+
# 自定义CSS样式
|
160 |
+
custom_css = """
|
161 |
+
h1, h2, h3, h4, h5, h6 {
|
162 |
+
font-family: 'Arial', sans-serif;
|
163 |
+
font-weight: bold;
|
164 |
+
}
|
165 |
+
body {
|
166 |
+
font-family: 'Arial', sans-serif;
|
167 |
+
}
|
168 |
+
.gradio-container {
|
169 |
+
max-width: 800px;
|
170 |
+
margin: auto;
|
171 |
+
padding: 20px;
|
172 |
+
border: 1px solid #ccc;
|
173 |
+
border-radius: 10px;
|
174 |
+
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
175 |
+
}
|
176 |
+
textarea, input, .btn-primary {
|
177 |
+
font-size: 16px !important;
|
178 |
+
padding: 10px !important;
|
179 |
+
border-radius: 5px !important;
|
180 |
+
}
|
181 |
+
#component-0 > .wrap > .block.markdown-block > .markdown {
|
182 |
+
font-size: 24px !important;
|
183 |
+
line-height: 1.8 !important;
|
184 |
+
}
|
185 |
+
"""
|
186 |
+
|
187 |
+
iface = gr.Interface(
|
188 |
+
fn=analyze_company,
|
189 |
+
inputs=gr.Textbox(lines=1, placeholder="输入英文公司名称或股票代码"),
|
190 |
+
outputs=gr.Markdown(label="Trade-Helper 股市基本面分析与预测"),
|
191 |
+
title="Trade-Helper",
|
192 |
+
description="请输入公司名称或股票代码,以获取AI的分析和预测报告。",
|
193 |
+
css=custom_css,
|
194 |
+
allow_flagging='never'
|
195 |
+
)
|
196 |
+
|
197 |
+
if __name__ == "__main__":
|
198 |
+
iface.launch(share=True)
|
setup.py
ADDED
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from setuptools import setup, find_packages
|
2 |
+
|
3 |
+
# Read requirements.txt, ignore comments
|
4 |
+
try:
|
5 |
+
with open("requirements.txt", "r") as f:
|
6 |
+
REQUIRES = [line.split("#", 1)[0].strip() for line in f if line.strip()]
|
7 |
+
except:
|
8 |
+
print("'requirements.txt' not found!")
|
9 |
+
REQUIRES = list()
|
10 |
+
|
11 |
+
setup(
|
12 |
+
name="FinRobot",
|
13 |
+
version="0.1.3",
|
14 |
+
include_package_data=True,
|
15 |
+
author="AI4Finance Foundation",
|
16 |
+
author_email="[email protected]",
|
17 |
+
url="https://github.com/AI4Finance-Foundation/FinRobot",
|
18 |
+
license="MIT",
|
19 |
+
packages=find_packages(),
|
20 |
+
install_requires=REQUIRES,
|
21 |
+
description="FinRobot: An Open-Source AI Agent Platform for Financial Applications using LLMs",
|
22 |
+
long_description="""FinRobot""",
|
23 |
+
classifiers=[
|
24 |
+
# Trove classifiers
|
25 |
+
# Full list: https://pypi.python.org/pypi?%3Aaction=list_classifiers
|
26 |
+
"License :: OSI Approved :: MIT License",
|
27 |
+
"Programming Language :: Python",
|
28 |
+
"Programming Language :: Python :: 3",
|
29 |
+
"Programming Language :: Python :: 3.6",
|
30 |
+
"Programming Language :: Python :: 3.7",
|
31 |
+
"Programming Language :: Python :: 3.8",
|
32 |
+
"Programming Language :: Python :: 3.9",
|
33 |
+
"Programming Language :: Python :: 3.10",
|
34 |
+
"Programming Language :: Python :: 3.11",
|
35 |
+
"Programming Language :: Python :: Implementation :: CPython",
|
36 |
+
"Programming Language :: Python :: Implementation :: PyPy",
|
37 |
+
],
|
38 |
+
keywords="Financial Large Language Models, AI Agents",
|
39 |
+
platforms=["any"],
|
40 |
+
python_requires=">=3.10, <3.12",
|
41 |
+
)
|
test_module.py
ADDED
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import backtrader as bt
|
2 |
+
|
3 |
+
|
4 |
+
# Create a Stratey
|
5 |
+
class TestStrategy(bt.Strategy):
|
6 |
+
params = (("exitbars", 5),)
|
7 |
+
|
8 |
+
def log(self, txt, dt=None):
|
9 |
+
"""Logging function fot this strategy"""
|
10 |
+
dt = dt or self.datas[0].datetime.date(0)
|
11 |
+
print("%s, %s" % (dt.isoformat(), txt))
|
12 |
+
|
13 |
+
def __init__(self):
|
14 |
+
# Keep a reference to the "close" line in the data[0] dataseries
|
15 |
+
self.dataclose = self.datas[0].close
|
16 |
+
|
17 |
+
# To keep track of pending orders and buy price/commission
|
18 |
+
self.order = None
|
19 |
+
self.buyprice = None
|
20 |
+
self.buycomm = None
|
21 |
+
|
22 |
+
def notify_order(self, order):
|
23 |
+
if order.status in [order.Submitted, order.Accepted]:
|
24 |
+
# Buy/Sell order submitted/accepted to/by broker - Nothing to do
|
25 |
+
return
|
26 |
+
|
27 |
+
# Check if an order has been completed
|
28 |
+
# Attention: broker could reject order if not enough cash
|
29 |
+
if order.status in [order.Completed]:
|
30 |
+
if order.isbuy():
|
31 |
+
self.log(
|
32 |
+
"BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f"
|
33 |
+
% (order.executed.price, order.executed.value, order.executed.comm)
|
34 |
+
)
|
35 |
+
|
36 |
+
self.buyprice = order.executed.price
|
37 |
+
self.buycomm = order.executed.comm
|
38 |
+
else: # Sell
|
39 |
+
self.log(
|
40 |
+
"SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f"
|
41 |
+
% (order.executed.price, order.executed.value, order.executed.comm)
|
42 |
+
)
|
43 |
+
|
44 |
+
self.bar_executed = len(self)
|
45 |
+
|
46 |
+
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
|
47 |
+
self.log("Order Canceled/Margin/Rejected")
|
48 |
+
|
49 |
+
self.order = None
|
50 |
+
|
51 |
+
def notify_trade(self, trade):
|
52 |
+
if not trade.isclosed:
|
53 |
+
return
|
54 |
+
|
55 |
+
self.log("OPERATION PROFIT, GROSS %.2f, NET %.2f" % (trade.pnl, trade.pnlcomm))
|
56 |
+
|
57 |
+
def next(self):
|
58 |
+
# Simply log the closing price of the series from the reference
|
59 |
+
self.log("Close, %.2f" % self.dataclose[0])
|
60 |
+
|
61 |
+
# Check if an order is pending ... if yes, we cannot send a 2nd one
|
62 |
+
if self.order:
|
63 |
+
return
|
64 |
+
|
65 |
+
# Check if we are in the market
|
66 |
+
if not self.position:
|
67 |
+
|
68 |
+
# Not yet ... we MIGHT BUY if ...
|
69 |
+
if self.dataclose[0] < self.dataclose[-1]:
|
70 |
+
# current close less than previous close
|
71 |
+
|
72 |
+
if self.dataclose[-1] < self.dataclose[-2]:
|
73 |
+
# previous close less than the previous close
|
74 |
+
|
75 |
+
# BUY, BUY, BUY!!! (with default parameters)
|
76 |
+
self.log("BUY CREATE, %.2f" % self.dataclose[0])
|
77 |
+
|
78 |
+
# Keep track of the created order to avoid a 2nd order
|
79 |
+
self.order = self.buy()
|
80 |
+
|
81 |
+
else:
|
82 |
+
|
83 |
+
# Already in the market ... we might sell
|
84 |
+
if len(self) >= (self.bar_executed + self.params.exitbars):
|
85 |
+
# SELL, SELL, SELL!!! (with all possible default parameters)
|
86 |
+
self.log("SELL CREATE, %.2f" % self.dataclose[0])
|
87 |
+
|
88 |
+
# Keep track of the created order to avoid a 2nd order
|
89 |
+
self.order = self.sell()
|
tutorials_advanced/agent_annual_report.ipynb
ADDED
The diff for this file is too large to render.
See raw diff
|
|
tutorials_advanced/agent_fingpt_forecaster.ipynb
ADDED
@@ -0,0 +1,503 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"cells": [
|
3 |
+
{
|
4 |
+
"cell_type": "markdown",
|
5 |
+
"metadata": {},
|
6 |
+
"source": [
|
7 |
+
"# FinGPT-Forecaster Re-implemented with FinRobot"
|
8 |
+
]
|
9 |
+
},
|
10 |
+
{
|
11 |
+
"cell_type": "markdown",
|
12 |
+
"metadata": {},
|
13 |
+
"source": [
|
14 |
+
"In this demo, we set up an agent to emulate the behavior of model in the fingpt-forecaster project with AutoGen, which takes a company's ticker symbol, recent basic financials and market news as input and predict its stock movements.\n",
|
15 |
+
"\n",
|
16 |
+
"For detail of the original project, check out [FinGPT-Forecaster](https://github.com/AI4Finance-Foundation/FinGPT/tree/master/fingpt/FinGPT_Forecaster)! 🔥[Demo](https://huggingface.co/spaces/FinGPT/FinGPT-Forecaster), [Medium Blog](https://medium.datadriveninvestor.com/introducing-fingpt-forecaster-the-future-of-robo-advisory-services-50add34e3d3c) & [Model](https://huggingface.co/FinGPT/fingpt-forecaster_dow30_llama2-7b_lora) on Huggingface🤗!"
|
17 |
+
]
|
18 |
+
},
|
19 |
+
{
|
20 |
+
"cell_type": "code",
|
21 |
+
"execution_count": 1,
|
22 |
+
"metadata": {},
|
23 |
+
"outputs": [],
|
24 |
+
"source": [
|
25 |
+
"import autogen\n",
|
26 |
+
"from autogen.cache import Cache\n",
|
27 |
+
"\n",
|
28 |
+
"from finrobot.utils import get_current_date, register_keys_from_json\n",
|
29 |
+
"from finrobot.data_source import FinnHubUtils, YFinanceUtils"
|
30 |
+
]
|
31 |
+
},
|
32 |
+
{
|
33 |
+
"cell_type": "markdown",
|
34 |
+
"metadata": {},
|
35 |
+
"source": [
|
36 |
+
"After importing all the necessary packages and functions, we first instantiate a market analysis assistant and a user proxy using the agent classes defined by AutoGen. We also need the config for OpenAI & Finnhub here. \n",
|
37 |
+
"- for openai configuration, rename OAI_CONFIG_LIST_sample to OAI_CONFIG_LIST and replace the api keys\n",
|
38 |
+
"- for finnhub configuration, rename config_api_keys_sample to config_api_keys and replace the api keys"
|
39 |
+
]
|
40 |
+
},
|
41 |
+
{
|
42 |
+
"cell_type": "code",
|
43 |
+
"execution_count": 2,
|
44 |
+
"metadata": {},
|
45 |
+
"outputs": [],
|
46 |
+
"source": [
|
47 |
+
"# Read OpenAI API keys from a JSON file\n",
|
48 |
+
"config_list = autogen.config_list_from_json(\n",
|
49 |
+
" \"../OAI_CONFIG_LIST\",\n",
|
50 |
+
" filter_dict={\"model\": [\"gpt-4-0125-preview\"]},\n",
|
51 |
+
")\n",
|
52 |
+
"llm_config = {\"config_list\": config_list, \"timeout\": 120, \"temperature\": 0}\n",
|
53 |
+
"\n",
|
54 |
+
"# Register FINNHUB API keys\n",
|
55 |
+
"register_keys_from_json(\"../config_api_keys\")"
|
56 |
+
]
|
57 |
+
},
|
58 |
+
{
|
59 |
+
"cell_type": "code",
|
60 |
+
"execution_count": 3,
|
61 |
+
"metadata": {},
|
62 |
+
"outputs": [],
|
63 |
+
"source": [
|
64 |
+
"analyst = autogen.AssistantAgent(\n",
|
65 |
+
" name=\"Market_Analyst\",\n",
|
66 |
+
" system_message=\"As a Market Analyst, one must possess strong analytical and problem-solving abilities, collect necessary financial information and aggregate them based on client's requirement.\"\n",
|
67 |
+
" \"For coding tasks, only use the functions you have been provided with. Reply TERMINATE when the task is done.\",\n",
|
68 |
+
" llm_config=llm_config,\n",
|
69 |
+
")\n",
|
70 |
+
"\n",
|
71 |
+
"user_proxy = autogen.UserProxyAgent(\n",
|
72 |
+
" name=\"User_Proxy\",\n",
|
73 |
+
" is_termination_msg=lambda x: x.get(\"content\", \"\") and x.get(\n",
|
74 |
+
" \"content\", \"\").endswith(\"TERMINATE\"),\n",
|
75 |
+
" human_input_mode=\"NEVER\",\n",
|
76 |
+
" max_consecutive_auto_reply=10,\n",
|
77 |
+
" code_execution_config={\n",
|
78 |
+
" \"work_dir\": \"coding\",\n",
|
79 |
+
" \"use_docker\": False,\n",
|
80 |
+
" }, # Please set use_docker=True if docker is available to run the generated code. Using docker is safer than running the generated code directly.\n",
|
81 |
+
")"
|
82 |
+
]
|
83 |
+
},
|
84 |
+
{
|
85 |
+
"cell_type": "markdown",
|
86 |
+
"metadata": {},
|
87 |
+
"source": [
|
88 |
+
"Then we register our predefined functions with the userproxy.\n",
|
89 |
+
"\n",
|
90 |
+
"Following our implementation with the fingpt-forecaster, we combined news/financials calls to the Finnhub API and stock data calls to the YFinance API."
|
91 |
+
]
|
92 |
+
},
|
93 |
+
{
|
94 |
+
"cell_type": "code",
|
95 |
+
"execution_count": 4,
|
96 |
+
"metadata": {},
|
97 |
+
"outputs": [],
|
98 |
+
"source": [
|
99 |
+
"from finrobot.toolkits import register_toolkits\n",
|
100 |
+
"\n",
|
101 |
+
"tools = [\n",
|
102 |
+
" {\n",
|
103 |
+
" \"function\": FinnHubUtils.get_company_profile,\n",
|
104 |
+
" \"name\": \"get_company_profile\",\n",
|
105 |
+
" \"description\": \"get a company's profile information\"\n",
|
106 |
+
" },\n",
|
107 |
+
" {\n",
|
108 |
+
" \"function\": FinnHubUtils.get_company_news,\n",
|
109 |
+
" \"name\": \"get_company_news\",\n",
|
110 |
+
" \"description\": \"retrieve market news related to designated company\"\n",
|
111 |
+
" },\n",
|
112 |
+
" {\n",
|
113 |
+
" \"function\": FinnHubUtils.get_basic_financials,\n",
|
114 |
+
" \"name\": \"get_financial_basics\",\n",
|
115 |
+
" \"description\": \"get latest financial basics for a designated company\"\n",
|
116 |
+
" },\n",
|
117 |
+
" {\n",
|
118 |
+
" \"function\": YFinanceUtils.get_stock_data,\n",
|
119 |
+
" \"name\": \"get_stock_data\",\n",
|
120 |
+
" \"description\": \"retrieve stock price data for designated ticker symbol\"\n",
|
121 |
+
" }\n",
|
122 |
+
"]\n",
|
123 |
+
"register_toolkits(tools, analyst, user_proxy)"
|
124 |
+
]
|
125 |
+
},
|
126 |
+
{
|
127 |
+
"cell_type": "markdown",
|
128 |
+
"metadata": {},
|
129 |
+
"source": [
|
130 |
+
"Start the conversation, asking the agent to call all the functions, and see how it aggregates all the information and leads to the conclusion."
|
131 |
+
]
|
132 |
+
},
|
133 |
+
{
|
134 |
+
"cell_type": "code",
|
135 |
+
"execution_count": 5,
|
136 |
+
"metadata": {},
|
137 |
+
"outputs": [
|
138 |
+
{
|
139 |
+
"name": "stdout",
|
140 |
+
"output_type": "stream",
|
141 |
+
"text": [
|
142 |
+
"\u001b[33mUser_Proxy\u001b[0m (to Market_Analyst):\n",
|
143 |
+
"\n",
|
144 |
+
"Use all the tools provided to retrieve information available for APPLE upon 2024-04-11. Analyze the positive developments and potential concerns of APPLE with 2-4 most important factors respectively and keep them concise. Most factors should be inferred from company related news. Then make a rough prediction (e.g. up/down by 2-3%) of the APPLE stock price movement for next week. Provide a summary analysis to support your prediction.\n",
|
145 |
+
"\n",
|
146 |
+
"--------------------------------------------------------------------------------\n",
|
147 |
+
"\u001b[33mMarket_Analyst\u001b[0m (to User_Proxy):\n",
|
148 |
+
"\n",
|
149 |
+
"\u001b[32m***** Suggested tool call (call_74K9pDrP0tgi2Su0GD4e0t1E): get_company_profile *****\u001b[0m\n",
|
150 |
+
"Arguments: \n",
|
151 |
+
"{\"symbol\": \"AAPL\"}\n",
|
152 |
+
"\u001b[32m************************************************************************************\u001b[0m\n",
|
153 |
+
"\u001b[32m***** Suggested tool call (call_BKKuzs9lNxWpwBNMkIOtTm8z): get_company_news *****\u001b[0m\n",
|
154 |
+
"Arguments: \n",
|
155 |
+
"{\"symbol\": \"AAPL\", \"start_date\": \"2024-04-04\", \"end_date\": \"2024-04-11\"}\n",
|
156 |
+
"\u001b[32m*********************************************************************************\u001b[0m\n",
|
157 |
+
"\u001b[32m***** Suggested tool call (call_mQTuLkVVcabsqBeOFkSQ83Oe): get_financial_basics *****\u001b[0m\n",
|
158 |
+
"Arguments: \n",
|
159 |
+
"{\"symbol\": \"AAPL\"}\n",
|
160 |
+
"\u001b[32m*************************************************************************************\u001b[0m\n",
|
161 |
+
"\u001b[32m***** Suggested tool call (call_KMaKxoAo0XalrinyToyj8Tss): get_stock_data *****\u001b[0m\n",
|
162 |
+
"Arguments: \n",
|
163 |
+
"{\"symbol\": \"AAPL\", \"start_date\": \"2024-03-11\", \"end_date\": \"2024-04-11\"}\n",
|
164 |
+
"\u001b[32m*******************************************************************************\u001b[0m\n",
|
165 |
+
"\n",
|
166 |
+
"--------------------------------------------------------------------------------\n",
|
167 |
+
"\u001b[35m\n",
|
168 |
+
">>>>>>>> EXECUTING FUNCTION get_company_profile...\u001b[0m\n",
|
169 |
+
"Finnhub client initialized\n",
|
170 |
+
"\u001b[35m\n",
|
171 |
+
">>>>>>>> EXECUTING FUNCTION get_company_news...\u001b[0m\n",
|
172 |
+
"Finnhub client initialized\n",
|
173 |
+
"\u001b[35m\n",
|
174 |
+
">>>>>>>> EXECUTING FUNCTION get_financial_basics...\u001b[0m\n",
|
175 |
+
"Finnhub client initialized\n",
|
176 |
+
"\u001b[35m\n",
|
177 |
+
">>>>>>>> EXECUTING FUNCTION get_stock_data...\u001b[0m\n"
|
178 |
+
]
|
179 |
+
},
|
180 |
+
{
|
181 |
+
"name": "stderr",
|
182 |
+
"output_type": "stream",
|
183 |
+
"text": [
|
184 |
+
"[*********************100%%**********************] 1 of 1 completed"
|
185 |
+
]
|
186 |
+
},
|
187 |
+
{
|
188 |
+
"name": "stdout",
|
189 |
+
"output_type": "stream",
|
190 |
+
"text": [
|
191 |
+
"\u001b[33mUser_Proxy\u001b[0m (to Market_Analyst):\n",
|
192 |
+
"\n",
|
193 |
+
"\u001b[33mUser_Proxy\u001b[0m (to Market_Analyst):\n",
|
194 |
+
"\n",
|
195 |
+
"\u001b[32m***** Response from calling tool (call_74K9pDrP0tgi2Su0GD4e0t1E) *****\u001b[0m\n",
|
196 |
+
"[Company Introduction]:\n",
|
197 |
+
"\n",
|
198 |
+
"Apple Inc is a leading entity in the Technology sector. Incorporated and publicly traded since 1980-12-12, the company has established its reputation as one of the key players in the market. As of today, Apple Inc has a market capitalization of 2590838.74 in USD, with 15441.88 shares outstanding.\n",
|
199 |
+
"\n",
|
200 |
+
"Apple Inc operates primarily in the US, trading under the ticker AAPL on the NASDAQ NMS - GLOBAL MARKET. As a dominant force in the Technology space, the company continues to innovate and drive progress within the industry.\n",
|
201 |
+
"\u001b[32m**********************************************************************\u001b[0m\n",
|
202 |
+
"\n",
|
203 |
+
"--------------------------------------------------------------------------------\n",
|
204 |
+
"\u001b[33mUser_Proxy\u001b[0m (to Market_Analyst):\n",
|
205 |
+
"\n",
|
206 |
+
"\u001b[32m***** Response from calling tool (call_BKKuzs9lNxWpwBNMkIOtTm8z) *****\u001b[0m\n",
|
207 |
+
" date headline summary\n",
|
208 |
+
"0 20240405122000 Europe is More Eager than U.S. to Cut Rates: This Could Hurt U.S. Multinationals Looking for stock market analysis and research with proves results? Zacks.com offers in-depth financial research with over 30years of proven results.\n",
|
209 |
+
"1 20240405122700 Europe Is More Eager Than the U.S. to Cut Rates: This Threatens U.S. Multinationals Looking for stock market analysis and research with proves results? Zacks.com offers in-depth financial research with over 30years of proven results.\n",
|
210 |
+
"2 20240405165700 Apple Layoffs 2024: What to Know About the Latest AAPL Job Cuts Looking for stock market analysis and research with proves results? Zacks.com offers in-depth financial research with over 30years of proven results.\n",
|
211 |
+
"3 20240405175913 Apple: Antitrust Case Could Prove Positive For Investors Apple is a world-class investor with one of the strongest brands whose stock is temporarily under pressure. Find out if now is a good time to buy AAPL stock.\n",
|
212 |
+
"4 20240405214500 1 Wall Street Analyst Thinks This Magnificent Seven Stock Is Going to $220. Is It a buy Around $169? The pundit feels that the company is doing a solid job of boosting its services revenue.\n",
|
213 |
+
"5 20240408193000 Microchip Technology expands TSMC partnership to strengthen supply chain Looking for stock market analysis and research with proves results? Zacks.com offers in-depth financial research with over 30years of proven results.\n",
|
214 |
+
"6 20240409090200 Apple changes strategy to allow retro game emulators on App Store Looking for stock market analysis and research with proves results? Zacks.com offers in-depth financial research with over 30years of proven results.\n",
|
215 |
+
"7 20240409150200 Analysts Offer Insights on Technology Companies: AmpliTech Group (AMPG) and Apple (AAPL) Looking for stock market analysis and research with proves results? Zacks.com offers in-depth financial research with over 30years of proven results.\n",
|
216 |
+
"8 20240409181500 1 Unstoppable Growth Stock to Buy With $1,000 Right Now This tech giant has more room to run.\n",
|
217 |
+
"9 20240410174500 Technology’s profits are “skewed to the downside” - Goldman Sachs Looking for stock market analysis and research with proves results? Zacks.com offers in-depth financial research with over 30years of proven results.\n",
|
218 |
+
"\u001b[32m**********************************************************************\u001b[0m\n",
|
219 |
+
"\n",
|
220 |
+
"--------------------------------------------------------------------------------\n",
|
221 |
+
"\u001b[33mUser_Proxy\u001b[0m (to Market_Analyst):\n",
|
222 |
+
"\n",
|
223 |
+
"\u001b[32m***** Response from calling tool (call_mQTuLkVVcabsqBeOFkSQ83Oe) *****\u001b[0m\n",
|
224 |
+
"{\n",
|
225 |
+
" \"10DayAverageTradingVolume\": 17.57643,\n",
|
226 |
+
" \"13WeekPriceReturnDaily\": -9.5818,\n",
|
227 |
+
" \"26WeekPriceReturnDaily\": -3.3859,\n",
|
228 |
+
" \"3MonthADReturnStd\": 19.35961,\n",
|
229 |
+
" \"3MonthAverageTradingVolume\": 18.83497,\n",
|
230 |
+
" \"52WeekHigh\": 199.615,\n",
|
231 |
+
" \"52WeekHighDate\": \"2023-12-14\",\n",
|
232 |
+
" \"52WeekLow\": 159.78,\n",
|
233 |
+
" \"52WeekLowDate\": \"2023-04-12\",\n",
|
234 |
+
" \"52WeekPriceReturnDaily\": 4.3408,\n",
|
235 |
+
" \"5DayPriceReturnDaily\": -0.616,\n",
|
236 |
+
" \"assetTurnoverAnnual\": 1.0871,\n",
|
237 |
+
" \"assetTurnoverTTM\": 1.1234,\n",
|
238 |
+
" \"beta\": 1.1331,\n",
|
239 |
+
" \"bookValuePerShareAnnual\": 3.9965,\n",
|
240 |
+
" \"bookValuePerShareQuarterly\": 4.7929,\n",
|
241 |
+
" \"bookValueShareGrowth5Y\": -6.64,\n",
|
242 |
+
" \"capexCagr5Y\": -3.82,\n",
|
243 |
+
" \"cashFlowPerShareAnnual\": 6.4041,\n",
|
244 |
+
" \"cashFlowPerShareQuarterly\": 6.9125,\n",
|
245 |
+
" \"cashFlowPerShareTTM\": 6.86253,\n",
|
246 |
+
" \"cashPerSharePerShareAnnual\": 3.9585,\n",
|
247 |
+
" \"cashPerSharePerShareQuarterly\": 4.7283,\n",
|
248 |
+
" \"currentDividendYieldTTM\": 0.5819,\n",
|
249 |
+
" \"currentEv/freeCashFlowAnnual\": 26.7046,\n",
|
250 |
+
" \"currentEv/freeCashFlowTTM\": 24.8842,\n",
|
251 |
+
" \"currentRatioAnnual\": 0.988,\n",
|
252 |
+
" \"currentRatioQuarterly\": 1.0725,\n",
|
253 |
+
" \"dividendGrowthRate5Y\": 6.58,\n",
|
254 |
+
" \"dividendPerShareAnnual\": 0.9542,\n",
|
255 |
+
" \"dividendPerShareTTM\": 0.9636,\n",
|
256 |
+
" \"dividendYieldIndicatedAnnual\": 0.56580424,\n",
|
257 |
+
" \"ebitdPerShareAnnual\": 7.9326,\n",
|
258 |
+
" \"ebitdPerShareTTM\": 8.2668,\n",
|
259 |
+
" \"ebitdaCagr5Y\": 9.04,\n",
|
260 |
+
" \"ebitdaInterimCagr5Y\": 9.94,\n",
|
261 |
+
" \"enterpriseValue\": 2659354,\n",
|
262 |
+
" \"epsAnnual\": 6.1339,\n",
|
263 |
+
" \"epsBasicExclExtraItemsAnnual\": 6.1339,\n",
|
264 |
+
" \"epsBasicExclExtraItemsTTM\": 6.427099999999999,\n",
|
265 |
+
" \"epsExclExtraItemsAnnual\": 6.1339,\n",
|
266 |
+
" \"epsExclExtraItemsTTM\": 6.427099999999999,\n",
|
267 |
+
" \"epsGrowth3Y\": 23.26,\n",
|
268 |
+
" \"epsGrowth5Y\": 15.56,\n",
|
269 |
+
" \"epsGrowthQuarterlyYoy\": 15.82,\n",
|
270 |
+
" \"epsGrowthTTMYoy\": 9.2,\n",
|
271 |
+
" \"epsInclExtraItemsAnnual\": 6.1339,\n",
|
272 |
+
" \"epsInclExtraItemsTTM\": 6.427099999999999,\n",
|
273 |
+
" \"epsNormalizedAnnual\": 6.1339,\n",
|
274 |
+
" \"epsTTM\": 6.427099999999999,\n",
|
275 |
+
" \"focfCagr5Y\": 9.2,\n",
|
276 |
+
" \"grossMargin5Y\": 41.05,\n",
|
277 |
+
" \"grossMarginAnnual\": 44.13,\n",
|
278 |
+
" \"grossMarginTTM\": 45.87,\n",
|
279 |
+
" \"inventoryTurnoverAnnual\": 37.9777,\n",
|
280 |
+
" \"inventoryTurnoverTTM\": 31.8108,\n",
|
281 |
+
" \"longTermDebt/equityAnnual\": 1.5332,\n",
|
282 |
+
" \"longTermDebt/equityQuarterly\": 1.2832,\n",
|
283 |
+
" \"marketCapitalization\": 2592074,\n",
|
284 |
+
" \"monthToDatePriceReturnDaily\": -2.1577,\n",
|
285 |
+
" \"netIncomeEmployeeAnnual\": 0.6025,\n",
|
286 |
+
" \"netIncomeEmployeeTTM\": 0.6268,\n",
|
287 |
+
" \"netInterestCoverageAnnual\": 622.5082,\n",
|
288 |
+
" \"netInterestCoverageTTM\": 622.5082,\n",
|
289 |
+
" \"netMarginGrowth5Y\": 2.46,\n",
|
290 |
+
" \"netProfitMargin5Y\": 23.73,\n",
|
291 |
+
" \"netProfitMarginAnnual\": 25.31,\n",
|
292 |
+
" \"netProfitMarginTTM\": 28.36,\n",
|
293 |
+
" \"operatingMargin5Y\": 27.72,\n",
|
294 |
+
" \"operatingMarginAnnual\": 29.82,\n",
|
295 |
+
" \"operatingMarginTTM\": 33.76,\n",
|
296 |
+
" \"payoutRatioAnnual\": 15.49,\n",
|
297 |
+
" \"payoutRatioTTM\": 0.1495,\n",
|
298 |
+
" \"pbAnnual\": 43.4754,\n",
|
299 |
+
" \"pbQuarterly\": 40.4099,\n",
|
300 |
+
" \"pcfShareAnnual\": 23.4486,\n",
|
301 |
+
" \"pcfShareTTM\": 22.2624,\n",
|
302 |
+
" \"peAnnual\": 26.7238,\n",
|
303 |
+
" \"peBasicExclExtraTTM\": 25.6862,\n",
|
304 |
+
" \"peExclExtraAnnual\": 30.96975,\n",
|
305 |
+
" \"peExclExtraTTM\": 25.6862,\n",
|
306 |
+
" \"peInclExtraTTM\": 25.6862,\n",
|
307 |
+
" \"peNormalizedAnnual\": 26.7238,\n",
|
308 |
+
" \"peTTM\": 29.6728,\n",
|
309 |
+
" \"pfcfShareAnnual\": 26.029,\n",
|
310 |
+
" \"pfcfShareTTM\": 24.2547,\n",
|
311 |
+
" \"pretaxMargin5Y\": 27.89,\n",
|
312 |
+
" \"pretaxMarginAnnual\": 29.67,\n",
|
313 |
+
" \"pretaxMarginTTM\": 33.72,\n",
|
314 |
+
" \"priceRelativeToS&P50013Week\": -17.9088,\n",
|
315 |
+
" \"priceRelativeToS&P50026Week\": -24.4522,\n",
|
316 |
+
" \"priceRelativeToS&P5004Week\": -1.5991,\n",
|
317 |
+
" \"priceRelativeToS&P50052Week\": -21.14,\n",
|
318 |
+
" \"priceRelativeToS&P500Ytd\": -21.0203,\n",
|
319 |
+
" \"psAnnual\": 6.7628,\n",
|
320 |
+
" \"psTTM\": 7.7634,\n",
|
321 |
+
" \"ptbvAnnual\": 4.8643,\n",
|
322 |
+
" \"ptbvQuarterly\": 6.4066,\n",
|
323 |
+
" \"quickRatioAnnual\": 0.9444,\n",
|
324 |
+
" \"quickRatioQuarterly\": 1.0239,\n",
|
325 |
+
" \"receivablesTurnoverAnnual\": 13.2873,\n",
|
326 |
+
" \"receivablesTurnoverTTM\": 16.4319,\n",
|
327 |
+
" \"revenueEmployeeAnnual\": 2.3807,\n",
|
328 |
+
" \"revenueEmployeeTTM\": 2.3957,\n",
|
329 |
+
" \"revenueGrowth3Y\": 11.77,\n",
|
330 |
+
" \"revenueGrowth5Y\": 7.61,\n",
|
331 |
+
" \"revenueGrowthQuarterlyYoy\": 2.07,\n",
|
332 |
+
" \"revenueGrowthTTMYoy\": -0.47,\n",
|
333 |
+
" \"revenuePerShareAnnual\": 24.2386,\n",
|
334 |
+
" \"revenuePerShareTTM\": 24.7618,\n",
|
335 |
+
" \"revenueShareGrowth5Y\": 12.79,\n",
|
336 |
+
" \"roa5Y\": 23.36,\n",
|
337 |
+
" \"roaRfy\": 27.51,\n",
|
338 |
+
" \"roaTTM\": 0.2939,\n",
|
339 |
+
" \"roe5Y\": 130.41,\n",
|
340 |
+
" \"roeRfy\": 156.07999999999998,\n",
|
341 |
+
" \"roeTTM\": 1.5604,\n",
|
342 |
+
" \"roi5Y\": 44.8,\n",
|
343 |
+
" \"roiAnnual\": 55.66,\n",
|
344 |
+
" \"roiTTM\": 57.940000000000005,\n",
|
345 |
+
" \"tangibleBookValuePerShareAnnual\": 5.8582,\n",
|
346 |
+
" \"tangibleBookValuePerShareQuarterly\": 6.7915,\n",
|
347 |
+
" \"tbvCagr5Y\": 11.34,\n",
|
348 |
+
" \"totalDebt/totalEquityAnnual\": 1.804,\n",
|
349 |
+
" \"totalDebt/totalEquityQuarterly\": 1.458,\n",
|
350 |
+
" \"yearToDatePriceReturnDaily\": -12.8551,\n",
|
351 |
+
" \"bookValue\": 74100,\n",
|
352 |
+
" \"cashRatio\": 0.30424040664910096,\n",
|
353 |
+
" \"currentRatio\": 1.0725,\n",
|
354 |
+
" \"ebitPerShare\": 2.5919,\n",
|
355 |
+
" \"eps\": 2.1774,\n",
|
356 |
+
" \"ev\": 3061651.2,\n",
|
357 |
+
" \"fcfMargin\": 0.3136,\n",
|
358 |
+
" \"fcfPerShareTTM\": 6.9125,\n",
|
359 |
+
" \"grossMargin\": 0.4587,\n",
|
360 |
+
" \"longtermDebtTotalAsset\": 0.269,\n",
|
361 |
+
" \"longtermDebtTotalCapital\": 0.5221,\n",
|
362 |
+
" \"longtermDebtTotalEquity\": 1.2832,\n",
|
363 |
+
" \"netDebtToTotalCapital\": 0.3694,\n",
|
364 |
+
" \"netDebtToTotalEquity\": 0.908,\n",
|
365 |
+
" \"netMargin\": 0.2836,\n",
|
366 |
+
" \"operatingMargin\": 0.3376,\n",
|
367 |
+
" \"pb\": 40.4099,\n",
|
368 |
+
" \"pfcfTTM\": 28.0191,\n",
|
369 |
+
" \"pretaxMargin\": 0.3372,\n",
|
370 |
+
" \"ptbv\": 6.4066,\n",
|
371 |
+
" \"quickRatio\": 1.0239,\n",
|
372 |
+
" \"roicTTM\": 0.5794,\n",
|
373 |
+
" \"rotcTTM\": 0.6813,\n",
|
374 |
+
" \"salesPerShare\": 7.6766,\n",
|
375 |
+
" \"sgaToSale\": 0.5413,\n",
|
376 |
+
" \"tangibleBookValue\": 138050,\n",
|
377 |
+
" \"totalDebtToEquity\": 1.458,\n",
|
378 |
+
" \"totalDebtToTotalAsset\": 0.3056,\n",
|
379 |
+
" \"totalDebtToTotalCapital\": 0.5932,\n",
|
380 |
+
" \"totalRatio\": 1.2652\n",
|
381 |
+
"}\n",
|
382 |
+
"\u001b[32m**********************************************************************\u001b[0m\n",
|
383 |
+
"\n",
|
384 |
+
"--------------------------------------------------------------------------------\n",
|
385 |
+
"\u001b[33mUser_Proxy\u001b[0m (to Market_Analyst):\n",
|
386 |
+
"\n",
|
387 |
+
"\u001b[32m***** Response from calling tool (call_KMaKxoAo0XalrinyToyj8Tss) *****\u001b[0m\n",
|
388 |
+
" Open High Low Close Adj Close Volume\n",
|
389 |
+
"Date \n",
|
390 |
+
"2024-03-11 172.940002 174.380005 172.050003 172.750000 172.750000 60139500\n",
|
391 |
+
"2024-03-12 173.149994 174.029999 171.009995 173.229996 173.229996 59825400\n",
|
392 |
+
"2024-03-13 172.770004 173.190002 170.759995 171.130005 171.130005 52488700\n",
|
393 |
+
"2024-03-14 172.910004 174.309998 172.050003 173.000000 173.000000 72913500\n",
|
394 |
+
"2024-03-15 171.169998 172.619995 170.289993 172.619995 172.619995 121664700\n",
|
395 |
+
"2024-03-18 175.570007 177.710007 173.520004 173.720001 173.720001 75604200\n",
|
396 |
+
"2024-03-19 174.339996 176.610001 173.029999 176.080002 176.080002 55215200\n",
|
397 |
+
"2024-03-20 175.720001 178.669998 175.089996 178.669998 178.669998 53423100\n",
|
398 |
+
"2024-03-21 177.050003 177.490005 170.839996 171.369995 171.369995 106181300\n",
|
399 |
+
"2024-03-22 171.759995 173.050003 170.059998 172.279999 172.279999 71106600\n",
|
400 |
+
"2024-03-25 170.570007 171.940002 169.449997 170.850006 170.850006 54288300\n",
|
401 |
+
"2024-03-26 170.000000 171.419998 169.580002 169.710007 169.710007 57388400\n",
|
402 |
+
"2024-03-27 170.410004 173.600006 170.110001 173.309998 173.309998 60273300\n",
|
403 |
+
"2024-03-28 171.750000 172.229996 170.509995 171.479996 171.479996 65672700\n",
|
404 |
+
"2024-04-01 171.190002 171.250000 169.479996 170.029999 170.029999 46240500\n",
|
405 |
+
"2024-04-02 169.080002 169.339996 168.229996 168.839996 168.839996 49329500\n",
|
406 |
+
"2024-04-03 168.789993 170.679993 168.580002 169.649994 169.649994 47691700\n",
|
407 |
+
"2024-04-04 170.289993 171.919998 168.820007 168.820007 168.820007 53704400\n",
|
408 |
+
"2024-04-05 169.589996 170.389999 168.949997 169.580002 169.580002 42055200\n",
|
409 |
+
"2024-04-08 169.029999 169.199997 168.240005 168.449997 168.449997 37425500\n",
|
410 |
+
"2024-04-09 168.699997 170.080002 168.350006 169.669998 169.669998 42451200\n",
|
411 |
+
"2024-04-10 168.800003 169.089996 167.110001 167.779999 167.779999 49587200\n",
|
412 |
+
"\u001b[32m**********************************************************************\u001b[0m\n",
|
413 |
+
"\n",
|
414 |
+
"--------------------------------------------------------------------------------\n"
|
415 |
+
]
|
416 |
+
},
|
417 |
+
{
|
418 |
+
"name": "stderr",
|
419 |
+
"output_type": "stream",
|
420 |
+
"text": [
|
421 |
+
"\n"
|
422 |
+
]
|
423 |
+
},
|
424 |
+
{
|
425 |
+
"name": "stdout",
|
426 |
+
"output_type": "stream",
|
427 |
+
"text": [
|
428 |
+
"\u001b[33mMarket_Analyst\u001b[0m (to User_Proxy):\n",
|
429 |
+
"\n",
|
430 |
+
"### Positive Developments for Apple\n",
|
431 |
+
"\n",
|
432 |
+
"1. **Expansion of Services Revenue**: A Wall Street analyst highlighted Apple's effective strategy in boosting its services revenue, indicating a solid performance in diversifying income sources beyond hardware sales. This is a positive sign for Apple's long-term growth prospects, as it shows the company's ability to innovate and expand in high-margin areas.\n",
|
433 |
+
"\n",
|
434 |
+
"2. **Strategic Changes in App Store Policy**: Apple's decision to allow retro game emulators on the App Store represents a strategic shift that could open up new revenue streams and enhance user engagement. This move could attract a niche but passionate segment of gamers, potentially increasing app sales and subscriptions.\n",
|
435 |
+
"\n",
|
436 |
+
"### Potential Concerns for Apple\n",
|
437 |
+
"\n",
|
438 |
+
"1. **Job Cuts**: The news about Apple layoffs in 2024 raises concerns about the company's operational efficiency and future growth prospects. Layoffs could indicate underlying issues such as cost pressures or a need to streamline operations in response to changing market dynamics.\n",
|
439 |
+
"\n",
|
440 |
+
"2. **Antitrust Case**: While the antitrust case against Apple could potentially prove positive for investors, as suggested by some analysts, it also poses a significant risk. Legal challenges can lead to uncertainties, potential financial penalties, and could force changes in Apple's business practices, impacting its profitability and market position.\n",
|
441 |
+
"\n",
|
442 |
+
"### Stock Price Movement Prediction for Next Week\n",
|
443 |
+
"\n",
|
444 |
+
"Given the mixed developments, with positive aspects like revenue diversification and strategic policy changes, against concerns such as layoffs and legal challenges, the prediction for Apple's stock price movement next week is cautiously optimistic. The company's strong brand and ability to innovate could outweigh the negatives in the short term.\n",
|
445 |
+
"\n",
|
446 |
+
"**Prediction**: Apple's stock price might see a slight uptick, potentially **up by 1-2%** over the next week. This prediction is supported by the company's solid job in boosting its services revenue and strategic adjustments, which could instill investor confidence despite the potential concerns.\n",
|
447 |
+
"\n",
|
448 |
+
"### Summary Analysis\n",
|
449 |
+
"\n",
|
450 |
+
"Apple's recent developments present a mixed bag of opportunities and challenges. The company's efforts to diversify its revenue streams and adapt to market demands are commendable and indicative of its robust strategic planning. However, operational challenges such as layoffs and ongoing legal battles could dampen investor sentiment. Considering the company's historical resilience and strategic moves to bolster revenue, a slight positive movement in stock price is anticipated. Nonetheless, investors should keep an eye on how Apple navigates its challenges, as these could influence its financial performance and stock valuation in the longer term.\n",
|
451 |
+
"\n",
|
452 |
+
"--------------------------------------------------------------------------------\n",
|
453 |
+
"\u001b[33mUser_Proxy\u001b[0m (to Market_Analyst):\n",
|
454 |
+
"\n",
|
455 |
+
"\n",
|
456 |
+
"\n",
|
457 |
+
"--------------------------------------------------------------------------------\n",
|
458 |
+
"\u001b[33mMarket_Analyst\u001b[0m (to User_Proxy):\n",
|
459 |
+
"\n",
|
460 |
+
"TERMINATE\n",
|
461 |
+
"\n",
|
462 |
+
"--------------------------------------------------------------------------------\n"
|
463 |
+
]
|
464 |
+
}
|
465 |
+
],
|
466 |
+
"source": [
|
467 |
+
"# company = \"Tesla\"\n",
|
468 |
+
"company = \"APPLE\"\n",
|
469 |
+
"\n",
|
470 |
+
"with Cache.disk() as cache:\n",
|
471 |
+
" # start the conversation\n",
|
472 |
+
" user_proxy.initiate_chat(\n",
|
473 |
+
" analyst,\n",
|
474 |
+
" message=f\"Use all the tools provided to retrieve information available for {company} upon {get_current_date()}. Analyze the positive developments and potential concerns of {company} \"\n",
|
475 |
+
" \"with 2-4 most important factors respectively and keep them concise. Most factors should be inferred from company related news. \"\n",
|
476 |
+
" f\"Then make a rough prediction (e.g. up/down by 2-3%) of the {company} stock price movement for next week. Provide a summary analysis to support your prediction.\",\n",
|
477 |
+
" cache=cache,\n",
|
478 |
+
" )"
|
479 |
+
]
|
480 |
+
}
|
481 |
+
],
|
482 |
+
"metadata": {
|
483 |
+
"kernelspec": {
|
484 |
+
"display_name": "finrobot",
|
485 |
+
"language": "python",
|
486 |
+
"name": "python3"
|
487 |
+
},
|
488 |
+
"language_info": {
|
489 |
+
"codemirror_mode": {
|
490 |
+
"name": "ipython",
|
491 |
+
"version": 3
|
492 |
+
},
|
493 |
+
"file_extension": ".py",
|
494 |
+
"mimetype": "text/x-python",
|
495 |
+
"name": "python",
|
496 |
+
"nbconvert_exporter": "python",
|
497 |
+
"pygments_lexer": "ipython3",
|
498 |
+
"version": "3.12.2"
|
499 |
+
}
|
500 |
+
},
|
501 |
+
"nbformat": 4,
|
502 |
+
"nbformat_minor": 2
|
503 |
+
}
|
tutorials_advanced/agent_trade_strategist.ipynb
ADDED
The diff for this file is too large to render.
See raw diff
|
|
tutorials_advanced/lmm_agent_mplfinance.ipynb
ADDED
The diff for this file is too large to render.
See raw diff
|
|
tutorials_advanced/lmm_agent_opt_smacross.ipynb
ADDED
The diff for this file is too large to render.
See raw diff
|
|
tutorials_advanced/servercn.py
ADDED
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import json
|
3 |
+
import pandas as pd
|
4 |
+
from datetime import date
|
5 |
+
import gradio as gr
|
6 |
+
import autogen
|
7 |
+
from autogen.cache import Cache
|
8 |
+
from finrobot.utils import get_current_date
|
9 |
+
from finrobot.data_source import FinnHubUtils, YFinanceUtils
|
10 |
+
from dotenv import load_dotenv
|
11 |
+
|
12 |
+
# Load environment variables from .env file
|
13 |
+
load_dotenv()
|
14 |
+
|
15 |
+
# Utility functions
|
16 |
+
def save_output(data: pd.DataFrame, tag: str, save_path: str = None) -> None:
|
17 |
+
if save_path:
|
18 |
+
data.to_csv(save_path)
|
19 |
+
print(f"{tag} saved to {save_path}")
|
20 |
+
|
21 |
+
def get_current_date():
|
22 |
+
return date.today().strftime("%Y-%m-%d")
|
23 |
+
|
24 |
+
def register_keys():
|
25 |
+
keys = {
|
26 |
+
"FINNHUB_API_KEY": os.getenv("FINNHUB_API_KEY"),
|
27 |
+
"FMP_API_KEY": os.getenv("FMP_API_KEY"),
|
28 |
+
"SEC_API_KEY": os.getenv("SEC_API_KEY")
|
29 |
+
}
|
30 |
+
for key, value in keys.items():
|
31 |
+
if value:
|
32 |
+
os.environ[key] = value
|
33 |
+
|
34 |
+
def read_response_from_md(filename):
|
35 |
+
with open(filename, "r") as file:
|
36 |
+
content = file.read()
|
37 |
+
return content
|
38 |
+
|
39 |
+
def save_to_md(content, filename):
|
40 |
+
with open(filename, "w") as file: # Use write mode to overwrite the file
|
41 |
+
file.write(content + "\n")
|
42 |
+
print(f"Content saved to {filename}")
|
43 |
+
|
44 |
+
# Initialize LLM configuration
|
45 |
+
config_list = [
|
46 |
+
{
|
47 |
+
"model": "gpt-4o",
|
48 |
+
"api_key": os.getenv("OPENAI_API_KEY")
|
49 |
+
}
|
50 |
+
]
|
51 |
+
llm_config = {"config_list": config_list, "timeout": 120, "temperature": 0}
|
52 |
+
|
53 |
+
# Register FINNHUB API keys
|
54 |
+
register_keys()
|
55 |
+
|
56 |
+
# Define agents
|
57 |
+
analyst = autogen.AssistantAgent(
|
58 |
+
name="Market_Analyst",
|
59 |
+
system_message="As a Market Analyst, one must possess strong analytical and problem-solving abilities, collect necessary financial information and aggregate them based on client's requirement. For coding tasks, only use the functions you have been provided with. Reply TERMINATE when the task is done.",
|
60 |
+
llm_config=llm_config,
|
61 |
+
)
|
62 |
+
|
63 |
+
user_proxy = autogen.UserProxyAgent(
|
64 |
+
name="User_Proxy",
|
65 |
+
is_termination_msg=lambda x: x.get("content", "") and x.get("content", "").strip().endswith("TERMINATE"),
|
66 |
+
human_input_mode="NEVER",
|
67 |
+
max_consecutive_auto_reply=10,
|
68 |
+
code_execution_config={
|
69 |
+
"work_dir": "coding",
|
70 |
+
"use_docker": False,
|
71 |
+
},
|
72 |
+
)
|
73 |
+
|
74 |
+
# Register tools
|
75 |
+
from finrobot.toolkits import register_toolkits
|
76 |
+
|
77 |
+
tools = [
|
78 |
+
{
|
79 |
+
"function": FinnHubUtils.get_company_profile,
|
80 |
+
"name": "get_company_profile",
|
81 |
+
"description": "get a company's profile information"
|
82 |
+
},
|
83 |
+
{
|
84 |
+
"function": FinnHubUtils.get_company_news,
|
85 |
+
"name": "get_company_news",
|
86 |
+
"description": "retrieve market news related to designated company"
|
87 |
+
},
|
88 |
+
{
|
89 |
+
"function": FinnHubUtils.get_basic_financials,
|
90 |
+
"name": "get_financial_basics",
|
91 |
+
"description": "get latest financial basics for a designated company"
|
92 |
+
},
|
93 |
+
{
|
94 |
+
"function": YFinanceUtils.get_stock_data,
|
95 |
+
"name": "get_stock_data",
|
96 |
+
"description": "retrieve stock price data for designated ticker symbol"
|
97 |
+
}
|
98 |
+
]
|
99 |
+
register_toolkits(tools, analyst, user_proxy)
|
100 |
+
|
101 |
+
def save_response_to_json(response, filename):
|
102 |
+
response_dict = {
|
103 |
+
"chat_id": response.chat_id,
|
104 |
+
"chat_history": response.chat_history,
|
105 |
+
"summary": response.summary,
|
106 |
+
"cost": response.cost,
|
107 |
+
"human_input": response.human_input
|
108 |
+
}
|
109 |
+
with open(filename, "w") as file:
|
110 |
+
file.write(json.dumps(response_dict, indent=4))
|
111 |
+
print(f"Response saved to {filename}")
|
112 |
+
|
113 |
+
# Function to initiate chat and save response
|
114 |
+
def initiate_chat_and_save_response(analyst, user_proxy, company):
|
115 |
+
today_date = get_current_date()
|
116 |
+
json_filename = f"result_{company}_{today_date}.json"
|
117 |
+
md_filename = f"result_{company}_{today_date}.md"
|
118 |
+
|
119 |
+
# Check if MD file already exists
|
120 |
+
if os.path.exists(md_filename):
|
121 |
+
return read_response_from_md(md_filename)
|
122 |
+
|
123 |
+
with Cache.disk() as cache:
|
124 |
+
response = user_proxy.initiate_chat(
|
125 |
+
analyst,
|
126 |
+
message=f"Report in Chinese. Use all the tools provided to retrieve information available for {company} upon {get_current_date()}. Analyze the positive developments and potential concerns of {company} with 2-4 most important factors respectively and keep them concise. Most factors should be inferred from company related news. Then make a rough prediction (e.g. up/down by %) of the {company} stock price movement for next week. Provide a summary analysis to support your prediction.",
|
127 |
+
cache=cache,
|
128 |
+
)
|
129 |
+
|
130 |
+
save_response_to_json(response, json_filename)
|
131 |
+
return json.dumps(response.chat_history, indent=4)
|
132 |
+
|
133 |
+
def filter_user_content(chat_history):
|
134 |
+
# 解析 chat_history 为 JSON 对象
|
135 |
+
chat_history_dict = json.loads(chat_history)
|
136 |
+
# 查找用户需要的内容
|
137 |
+
for entry in chat_history_dict:
|
138 |
+
if entry['role'] == 'user' and "###" in entry['content']:
|
139 |
+
return entry['content']
|
140 |
+
return "No relevant content found."
|
141 |
+
|
142 |
+
# 使用更新的函数在 analyze_company 中
|
143 |
+
def analyze_company(company):
|
144 |
+
if company:
|
145 |
+
company = company.upper()
|
146 |
+
today_date = get_current_date()
|
147 |
+
md_filename = f"result_{company}_{today_date}.md"
|
148 |
+
|
149 |
+
# Check if MD file already exists
|
150 |
+
if os.path.exists(md_filename):
|
151 |
+
return read_response_from_md(md_filename)
|
152 |
+
|
153 |
+
content = initiate_chat_and_save_response(analyst, user_proxy, company)
|
154 |
+
# 筛选有效的用户内容
|
155 |
+
filtered_content = filter_user_content(content)
|
156 |
+
save_to_md(filtered_content, md_filename) # 只保存筛选后的内容
|
157 |
+
return filtered_content
|
158 |
+
|
159 |
+
# 自定义CSS样式
|
160 |
+
custom_css = """
|
161 |
+
h1, h2, h3, h4, h5, h6 {
|
162 |
+
font-family: 'Arial', sans-serif;
|
163 |
+
font-weight: bold;
|
164 |
+
}
|
165 |
+
body {
|
166 |
+
font-family: 'Arial', sans-serif;
|
167 |
+
}
|
168 |
+
.gradio-container {
|
169 |
+
max-width: 800px;
|
170 |
+
margin: auto;
|
171 |
+
padding: 20px;
|
172 |
+
border: 1px solid #ccc;
|
173 |
+
border-radius: 10px;
|
174 |
+
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
175 |
+
}
|
176 |
+
textarea, input, .btn-primary {
|
177 |
+
font-size: 16px !important;
|
178 |
+
padding: 10px !important;
|
179 |
+
border-radius: 5px !important;
|
180 |
+
}
|
181 |
+
#component-0 > .wrap > .block.markdown-block > .markdown {
|
182 |
+
font-size: 24px !important;
|
183 |
+
line-height: 1.8 !important;
|
184 |
+
}
|
185 |
+
"""
|
186 |
+
|
187 |
+
iface = gr.Interface(
|
188 |
+
fn=analyze_company,
|
189 |
+
inputs=gr.Textbox(lines=1, placeholder="输入英文公司名称或股票代码"),
|
190 |
+
outputs=gr.Markdown(label="Trade-Helper 股市基本面分析与预测"),
|
191 |
+
title="Trade-Helper",
|
192 |
+
description="请输入公司名称或股票代码,以获取AI的分析和预测报告。",
|
193 |
+
css=custom_css,
|
194 |
+
allow_flagging='never'
|
195 |
+
)
|
196 |
+
|
197 |
+
if __name__ == "__main__":
|
198 |
+
iface.launch(share=True)
|
tutorials_advanced/serveren.py
ADDED
@@ -0,0 +1,199 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import json
|
3 |
+
import pandas as pd
|
4 |
+
from datetime import date
|
5 |
+
import gradio as gr
|
6 |
+
import autogen
|
7 |
+
from autogen.cache import Cache
|
8 |
+
from finrobot.utils import get_current_date
|
9 |
+
from finrobot.data_source import FinnHubUtils, YFinanceUtils
|
10 |
+
from dotenv import load_dotenv
|
11 |
+
|
12 |
+
# Load environment variables from .env file
|
13 |
+
load_dotenv()
|
14 |
+
|
15 |
+
# Utility functions
|
16 |
+
def save_output(data: pd.DataFrame, tag: str, save_path: str = None) -> None:
|
17 |
+
if save_path:
|
18 |
+
data.to_csv(save_path)
|
19 |
+
print(f"{tag} saved to {save_path}")
|
20 |
+
|
21 |
+
def get_current_date():
|
22 |
+
return date.today().strftime("%Y-%m-%d")
|
23 |
+
|
24 |
+
def register_keys():
|
25 |
+
keys = {
|
26 |
+
"FINNHUB_API_KEY": os.getenv("FINNHUB_API_KEY"),
|
27 |
+
"FMP_API_KEY": os.getenv("FMP_API_KEY"),
|
28 |
+
"SEC_API_KEY": os.getenv("SEC_API_KEY")
|
29 |
+
}
|
30 |
+
for key, value in keys.items():
|
31 |
+
if value:
|
32 |
+
os.environ[key] = value
|
33 |
+
|
34 |
+
def read_response_from_md(filename):
|
35 |
+
with open(filename, "r") as file:
|
36 |
+
content = file.read()
|
37 |
+
return content
|
38 |
+
|
39 |
+
def save_to_md(content, filename):
|
40 |
+
with open(filename, "w") as file: # Use write mode to overwrite the file
|
41 |
+
file.write(content + "\n")
|
42 |
+
print(f"Content saved to {filename}")
|
43 |
+
|
44 |
+
# Initialize LLM configuration
|
45 |
+
config_list = [
|
46 |
+
{
|
47 |
+
"model": "gpt-4o",
|
48 |
+
"api_key": os.getenv("OPENAI_API_KEY")
|
49 |
+
}
|
50 |
+
]
|
51 |
+
llm_config = {"config_list": config_list, "timeout": 120, "temperature": 0}
|
52 |
+
|
53 |
+
# Register FINNHUB API keys
|
54 |
+
register_keys()
|
55 |
+
|
56 |
+
# Define agents
|
57 |
+
analyst = autogen.AssistantAgent(
|
58 |
+
name="Market_Analyst",
|
59 |
+
system_message="As a Market Analyst, one must possess strong analytical and problem-solving abilities, collect necessary financial information and aggregate them based on client's requirement. For coding tasks, only use the functions you have been provided with. Reply TERMINATE when the task is done.",
|
60 |
+
llm_config=llm_config,
|
61 |
+
)
|
62 |
+
|
63 |
+
user_proxy = autogen.UserProxyAgent(
|
64 |
+
name="User_Proxy",
|
65 |
+
is_termination_msg=lambda x: x.get("content", "") and x.get("content", "").strip().endswith("TERMINATE"),
|
66 |
+
human_input_mode="NEVER",
|
67 |
+
max_consecutive_auto_reply=10,
|
68 |
+
code_execution_config={
|
69 |
+
"work_dir": "coding",
|
70 |
+
"use_docker": False,
|
71 |
+
},
|
72 |
+
)
|
73 |
+
|
74 |
+
# Register tools
|
75 |
+
from finrobot.toolkits import register_toolkits
|
76 |
+
|
77 |
+
tools = [
|
78 |
+
{
|
79 |
+
"function": FinnHubUtils.get_company_profile,
|
80 |
+
"name": "get_company_profile",
|
81 |
+
"description": "get a company's profile information"
|
82 |
+
},
|
83 |
+
{
|
84 |
+
"function": FinnHubUtils.get_company_news,
|
85 |
+
"name": "get_company_news",
|
86 |
+
"description": "retrieve market news related to designated company"
|
87 |
+
},
|
88 |
+
{
|
89 |
+
"function": FinnHubUtils.get_basic_financials,
|
90 |
+
"name": "get_financial_basics",
|
91 |
+
"description": "get latest financial basics for a designated company"
|
92 |
+
},
|
93 |
+
{
|
94 |
+
"function": YFinanceUtils.get_stock_data,
|
95 |
+
"name": "get_stock_data",
|
96 |
+
"description": "retrieve stock price data for designated ticker symbol"
|
97 |
+
}
|
98 |
+
]
|
99 |
+
register_toolkits(tools, analyst, user_proxy)
|
100 |
+
|
101 |
+
def save_response_to_json(response, filename):
|
102 |
+
response_dict = {
|
103 |
+
"chat_id": response.chat_id,
|
104 |
+
"chat_history": response.chat_history,
|
105 |
+
"summary": response.summary,
|
106 |
+
"cost": response.cost,
|
107 |
+
"human_input": response.human_input
|
108 |
+
}
|
109 |
+
with open(filename, "w") as file:
|
110 |
+
file.write(json.dumps(response_dict, indent=4))
|
111 |
+
print(f"Response saved to {filename}")
|
112 |
+
|
113 |
+
# Function to initiate chat and save response
|
114 |
+
def initiate_chat_and_save_response(analyst, user_proxy, company):
|
115 |
+
today_date = get_current_date()
|
116 |
+
json_filename = f"result_{company}_{today_date}.json"
|
117 |
+
md_filename = f"result_{company}_{today_date}.md"
|
118 |
+
|
119 |
+
# Check if MD file already exists
|
120 |
+
if os.path.exists(md_filename):
|
121 |
+
return read_response_from_md(md_filename)
|
122 |
+
|
123 |
+
with Cache.disk() as cache:
|
124 |
+
response = user_proxy.initiate_chat(
|
125 |
+
analyst,
|
126 |
+
message=f"Use all the tools provided to retrieve information available for {company} upon {get_current_date()}. Analyze the positive developments and potential concerns of {company} with 2-4 most important factors respectively and keep them concise. Most factors should be inferred from company related news. Then make a rough prediction (e.g. up/down by %) of the {company} stock price movement for next week. Provide a summary analysis to support your prediction.",
|
127 |
+
cache=cache,
|
128 |
+
)
|
129 |
+
|
130 |
+
save_response_to_json(response, json_filename)
|
131 |
+
return json.dumps(response.chat_history, indent=4)
|
132 |
+
|
133 |
+
def filter_user_content(chat_history):
|
134 |
+
# 解析 chat_history 为 JSON 对象
|
135 |
+
chat_history_dict = json.loads(chat_history)
|
136 |
+
# 查找用户需要的内容
|
137 |
+
for entry in chat_history_dict:
|
138 |
+
if entry['role'] == 'user' and "###" in entry['content']:
|
139 |
+
return entry['content']
|
140 |
+
return "No relevant content found."
|
141 |
+
|
142 |
+
# 使用更新的函数在 analyze_company 中
|
143 |
+
def analyze_company(company):
|
144 |
+
if company:
|
145 |
+
company = company.upper()
|
146 |
+
today_date = get_current_date()
|
147 |
+
md_filename = f"result_{company}_{today_date}.md"
|
148 |
+
|
149 |
+
# Check if MD file already exists
|
150 |
+
if os.path.exists(md_filename):
|
151 |
+
return read_response_from_md(md_filename)
|
152 |
+
|
153 |
+
content = initiate_chat_and_save_response(analyst, user_proxy, company)
|
154 |
+
# 筛选有效的用户内容
|
155 |
+
filtered_content = filter_user_content(content)
|
156 |
+
save_to_md(filtered_content, md_filename) # 只保存筛选后的内容
|
157 |
+
return filtered_content
|
158 |
+
|
159 |
+
# 自定义CSS样式
|
160 |
+
custom_css = """
|
161 |
+
h1, h2, h3, h4, h5, h6 {
|
162 |
+
font-family: 'Arial', sans-serif;
|
163 |
+
font-weight: bold;
|
164 |
+
}
|
165 |
+
body {
|
166 |
+
font-family: 'Arial', sans-serif;
|
167 |
+
}
|
168 |
+
.gradio-container {
|
169 |
+
max-width: 800px;
|
170 |
+
margin: auto;
|
171 |
+
padding: 20px;
|
172 |
+
border: 1px solid #ccc;
|
173 |
+
border-radius: 10px;
|
174 |
+
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
175 |
+
}
|
176 |
+
textarea, input, .btn-primary {
|
177 |
+
font-size: 16px !important;
|
178 |
+
padding: 10px !important;
|
179 |
+
border-radius: 5px !important;
|
180 |
+
}
|
181 |
+
#component-0 > .wrap > .block.markdown-block > .markdown {
|
182 |
+
font-size: 24px !important;
|
183 |
+
line-height: 1.8 !important;
|
184 |
+
}
|
185 |
+
"""
|
186 |
+
|
187 |
+
# Gradio接口
|
188 |
+
iface = gr.Interface(
|
189 |
+
fn=analyze_company,
|
190 |
+
inputs=gr.Textbox(lines=1, placeholder="Enter company name or stock code"),
|
191 |
+
outputs=gr.Markdown(label="Trade-Helper"),
|
192 |
+
title="Trade-Helper",
|
193 |
+
description="Enter the company name or stock code to get a AI-Powered analysis and forcast prediction.",
|
194 |
+
css=custom_css,
|
195 |
+
allow_flagging='never'
|
196 |
+
)
|
197 |
+
|
198 |
+
if __name__ == "__main__":
|
199 |
+
iface.launch(share=True)
|
tutorials_beginner/agent_annual_report.ipynb
ADDED
The diff for this file is too large to render.
See raw diff
|
|
tutorials_beginner/agent_fingpt_forecaster.ipynb
ADDED
@@ -0,0 +1,395 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"cells": [
|
3 |
+
{
|
4 |
+
"cell_type": "markdown",
|
5 |
+
"metadata": {},
|
6 |
+
"source": [
|
7 |
+
"# FinGPT-Forecaster Re-implemented with FinRobot"
|
8 |
+
]
|
9 |
+
},
|
10 |
+
{
|
11 |
+
"cell_type": "markdown",
|
12 |
+
"metadata": {},
|
13 |
+
"source": [
|
14 |
+
"In this demo, we set up an agent to emulate the behavior of model in the fingpt-forecaster project with AutoGen, which takes a company's ticker symbol, recent basic financials and market news as input and predict its stock movements.\n",
|
15 |
+
"\n",
|
16 |
+
"For detail of the original project, check out [FinGPT-Forecaster](https://github.com/AI4Finance-Foundation/FinGPT/tree/master/fingpt/FinGPT_Forecaster)! 🔥[Demo](https://huggingface.co/spaces/FinGPT/FinGPT-Forecaster), [Medium Blog](https://medium.datadriveninvestor.com/introducing-fingpt-forecaster-the-future-of-robo-advisory-services-50add34e3d3c) & [Model](https://huggingface.co/FinGPT/fingpt-forecaster_dow30_llama2-7b_lora) on Huggingface🤗!\n",
|
17 |
+
"\n",
|
18 |
+
"This is a default bot, for more configurable demo, see [advanced tutorial](../tutorials_advanced/agent_fingpt_forecaster.ipynb)"
|
19 |
+
]
|
20 |
+
},
|
21 |
+
{
|
22 |
+
"cell_type": "code",
|
23 |
+
"execution_count": 1,
|
24 |
+
"metadata": {},
|
25 |
+
"outputs": [],
|
26 |
+
"source": [
|
27 |
+
"import autogen\n",
|
28 |
+
"from finrobot.utils import get_current_date, register_keys_from_json\n",
|
29 |
+
"from finrobot.agents.workflow import SingleAssistant"
|
30 |
+
]
|
31 |
+
},
|
32 |
+
{
|
33 |
+
"cell_type": "markdown",
|
34 |
+
"metadata": {},
|
35 |
+
"source": [
|
36 |
+
"After importing all the necessary packages and functions, we instantiate a SingleAssistant workflow \"Market_Analyst\".\n",
|
37 |
+
"We also need the config for OpenAI & Finnhub here. \n",
|
38 |
+
"- for openai configuration, rename OAI_CONFIG_LIST_sample to OAI_CONFIG_LIST and replace the api keys\n",
|
39 |
+
"- for finnhub configuration, rename config_api_keys_sample to config_api_keys and replace the api keys"
|
40 |
+
]
|
41 |
+
},
|
42 |
+
{
|
43 |
+
"cell_type": "code",
|
44 |
+
"execution_count": 2,
|
45 |
+
"metadata": {},
|
46 |
+
"outputs": [],
|
47 |
+
"source": [
|
48 |
+
"# Read OpenAI API keys from a JSON file\n",
|
49 |
+
"llm_config = {\n",
|
50 |
+
" \"config_list\": autogen.config_list_from_json(\n",
|
51 |
+
" \"../OAI_CONFIG_LIST\",\n",
|
52 |
+
" filter_dict={\"model\": [\"gpt-4-0125-preview\"]},\n",
|
53 |
+
" ),\n",
|
54 |
+
" \"timeout\": 120,\n",
|
55 |
+
" \"temperature\": 0,\n",
|
56 |
+
"}\n",
|
57 |
+
"\n",
|
58 |
+
"# Register FINNHUB API keys\n",
|
59 |
+
"register_keys_from_json(\"../config_api_keys\")"
|
60 |
+
]
|
61 |
+
},
|
62 |
+
{
|
63 |
+
"cell_type": "markdown",
|
64 |
+
"metadata": {},
|
65 |
+
"source": [
|
66 |
+
"Define the assistant, and simply start chatting!"
|
67 |
+
]
|
68 |
+
},
|
69 |
+
{
|
70 |
+
"cell_type": "code",
|
71 |
+
"execution_count": null,
|
72 |
+
"metadata": {},
|
73 |
+
"outputs": [
|
74 |
+
{
|
75 |
+
"name": "stdout",
|
76 |
+
"output_type": "stream",
|
77 |
+
"text": [
|
78 |
+
"\u001b[33mUser_Proxy\u001b[0m (to Market_Analyst):\n",
|
79 |
+
"\n",
|
80 |
+
"Use all the tools provided to retrieve information available for APPLE upon 2024-05-23. Analyze the positive developments and potential concerns of APPLE with 2-4 most important factors respectively and keep them concise. Most factors should be inferred from company related news. Then make a rough prediction (e.g. up/down by 2-3%) of the APPLE stock price movement for next week. Provide a summary analysis to support your prediction.\n",
|
81 |
+
"\n",
|
82 |
+
"--------------------------------------------------------------------------------\n",
|
83 |
+
"\u001b[33mMarket_Analyst\u001b[0m (to User_Proxy):\n",
|
84 |
+
"\n",
|
85 |
+
"\u001b[32m***** Suggested tool call (call_5xXm48aC8GmhlHfygA8zWO7Z): get_company_profile *****\u001b[0m\n",
|
86 |
+
"Arguments: \n",
|
87 |
+
"{\"symbol\": \"AAPL\"}\n",
|
88 |
+
"\u001b[32m************************************************************************************\u001b[0m\n",
|
89 |
+
"\u001b[32m***** Suggested tool call (call_BGHV5ASEpZCGcG3z5K49TlEW): get_company_news *****\u001b[0m\n",
|
90 |
+
"Arguments: \n",
|
91 |
+
"{\"symbol\": \"AAPL\", \"start_date\": \"2024-05-16\", \"end_date\": \"2024-05-23\"}\n",
|
92 |
+
"\u001b[32m*********************************************************************************\u001b[0m\n",
|
93 |
+
"\u001b[32m***** Suggested tool call (call_YfjiPBOstzubJdzdCyCnGhfv): get_financial_basics *****\u001b[0m\n",
|
94 |
+
"Arguments: \n",
|
95 |
+
"{\"symbol\": \"AAPL\"}\n",
|
96 |
+
"\u001b[32m*************************************************************************************\u001b[0m\n",
|
97 |
+
"\u001b[32m***** Suggested tool call (call_GPCnZaJrmUixSgPp5x9rgHs0): get_stock_data *****\u001b[0m\n",
|
98 |
+
"Arguments: \n",
|
99 |
+
"{\"symbol\": \"AAPL\", \"start_date\": \"2024-05-16\", \"end_date\": \"2024-05-23\"}\n",
|
100 |
+
"\u001b[32m*******************************************************************************\u001b[0m\n",
|
101 |
+
"\n",
|
102 |
+
"--------------------------------------------------------------------------------\n",
|
103 |
+
"\u001b[35m\n",
|
104 |
+
">>>>>>>> EXECUTING FUNCTION get_company_profile...\u001b[0m\n",
|
105 |
+
"Finnhub client initialized\n",
|
106 |
+
"\u001b[35m\n",
|
107 |
+
">>>>>>>> EXECUTING FUNCTION get_company_news...\u001b[0m\n",
|
108 |
+
"Finnhub client initialized\n",
|
109 |
+
"\u001b[35m\n",
|
110 |
+
">>>>>>>> EXECUTING FUNCTION get_financial_basics...\u001b[0m\n",
|
111 |
+
"Finnhub client initialized\n",
|
112 |
+
"\u001b[35m\n",
|
113 |
+
">>>>>>>> EXECUTING FUNCTION get_stock_data...\u001b[0m\n",
|
114 |
+
"\u001b[33mUser_Proxy\u001b[0m (to Market_Analyst):\n",
|
115 |
+
"\n",
|
116 |
+
"\u001b[33mUser_Proxy\u001b[0m (to Market_Analyst):\n",
|
117 |
+
"\n",
|
118 |
+
"\u001b[32m***** Response from calling tool (call_5xXm48aC8GmhlHfygA8zWO7Z) *****\u001b[0m\n",
|
119 |
+
"[Company Introduction]:\n",
|
120 |
+
"\n",
|
121 |
+
"Apple Inc is a leading entity in the Technology sector. Incorporated and publicly traded since 1980-12-12, the company has established its reputation as one of the key players in the market. As of today, Apple Inc has a market capitalization of 2927276.17 in USD, with 15334.08 shares outstanding.\n",
|
122 |
+
"\n",
|
123 |
+
"Apple Inc operates primarily in the US, trading under the ticker AAPL on the NASDAQ NMS - GLOBAL MARKET. As a dominant force in the Technology space, the company continues to innovate and drive progress within the industry.\n",
|
124 |
+
"\u001b[32m**********************************************************************\u001b[0m\n",
|
125 |
+
"\n",
|
126 |
+
"--------------------------------------------------------------------------------\n",
|
127 |
+
"\u001b[33mUser_Proxy\u001b[0m (to Market_Analyst):\n",
|
128 |
+
"\n",
|
129 |
+
"\u001b[32m***** Response from calling tool (call_BGHV5ASEpZCGcG3z5K49TlEW) *****\u001b[0m\n",
|
130 |
+
" date headline summary\n",
|
131 |
+
"0 20240112064122 The Biggest Bitcoin ETF Threat No One Is Talking About A majority of bitcoin ETF issuers have chosen Coinbase as a custodian, which is a concentration of risk. Even if that is the safest option, new cybersecurity standards are needed for making crypto custody truly safe.\n",
|
132 |
+
"1 20240517093800 Harbor Capital Appreciation Fund's 1st-Quarter Commentary: A Review Looking for stock market analysis and research with proves results? Zacks.com offers in-depth financial research with over 30years of proven results.\n",
|
133 |
+
"2 20240517173300 Market Clubhouse Morning Memo - May 17th, 2024 (Trade Strategy For SPY, QQQ, AAPL, MSFT, NVDA, GOOGL, META And TSLA) Looking for stock market analysis and research with proves results? Zacks.com offers in-depth financial research with over 30years of proven results.\n",
|
134 |
+
"3 20240518143623 Building A $50,000 Dividend Portfolio With 3 ETFs And 3 High Dividend Yield Stocks Check here to learn how to build a diversified dividend portfolio with ETFs and single companies, offering stable income and attractive total returns for investors.\n",
|
135 |
+
"4 20240519210000 Facebook Parent’s Plan to Win AI Race: Give Its Tech Away Free Meta Platforms CEO Mark Zuckerberg has trained his “Eye of Sauron” on its artificial-intelligence strategy, which comes with risks.\n",
|
136 |
+
"5 20240520183000 Forget Apple: This Other Buyback Stock Soared 28% Over the Last Year, and Could Be Headed Even Higher Apple made headlines following its announced $110 billion share buyback, but another company also buying back stock may be the better opportunity.\n",
|
137 |
+
"6 20240521090900 Microsoft's latest tools turn anyone into software developer: Build 2024 Looking for stock market analysis and research with proves results? Zacks.com offers in-depth financial research with over 30years of proven results.\n",
|
138 |
+
"7 20240521105000 3 Stocks That Could Be the Next Apple, Amazon, or Google by 2034 Looking for stock market analysis and research with proves results? Zacks.com offers in-depth financial research with over 30years of proven results.\n",
|
139 |
+
"8 20240522090000 ‘Hold Music’ Is Annoying. It Doesn’t Have to Be That Way. Companies really don’t give that much thought to what we listen to as we wait. They should start paying more attention.\n",
|
140 |
+
"9 20240522170600 Here Are All 44 Stocks Warren Buffett Holds for Berkshire Hathaway's $378 Billion Portfolio Despite holding stakes in close to four-dozen stocks, 82% of Berkshire's $378 billion of invested assets is concentrated in eight core companies.\n",
|
141 |
+
"\u001b[32m**********************************************************************\u001b[0m\n",
|
142 |
+
"\n",
|
143 |
+
"--------------------------------------------------------------------------------\n",
|
144 |
+
"\u001b[33mUser_Proxy\u001b[0m (to Market_Analyst):\n",
|
145 |
+
"\n",
|
146 |
+
"\u001b[32m***** Response from calling tool (call_YfjiPBOstzubJdzdCyCnGhfv) *****\u001b[0m\n",
|
147 |
+
"{\n",
|
148 |
+
" \"10DayAverageTradingVolume\": 17.2618,\n",
|
149 |
+
" \"13WeekPriceReturnDaily\": 4.706,\n",
|
150 |
+
" \"26WeekPriceReturnDaily\": 1.5372,\n",
|
151 |
+
" \"3MonthADReturnStd\": 24.229355,\n",
|
152 |
+
" \"3MonthAverageTradingVolume\": 20.63892,\n",
|
153 |
+
" \"52WeekHigh\": 199.615,\n",
|
154 |
+
" \"52WeekHighDate\": \"2023-12-14\",\n",
|
155 |
+
" \"52WeekLow\": 164.08,\n",
|
156 |
+
" \"52WeekLowDate\": \"2024-04-19\",\n",
|
157 |
+
" \"52WeekPriceReturnDaily\": 11.273,\n",
|
158 |
+
" \"5DayPriceReturnDaily\": 0.5584,\n",
|
159 |
+
" \"assetTurnoverAnnual\": 1.0871,\n",
|
160 |
+
" \"assetTurnoverTTM\": 1.1073,\n",
|
161 |
+
" \"beta\": 1.1417378,\n",
|
162 |
+
" \"bookValuePerShareAnnual\": 3.9965,\n",
|
163 |
+
" \"bookValuePerShareQuarterly\": 4.8374,\n",
|
164 |
+
" \"bookValueShareGrowth5Y\": -6.64,\n",
|
165 |
+
" \"capexCagr5Y\": -3.82,\n",
|
166 |
+
" \"cashFlowPerShareAnnual\": 6.4041,\n",
|
167 |
+
" \"cashFlowPerShareQuarterly\": 6.645,\n",
|
168 |
+
" \"cashFlowPerShareTTM\": 6.86253,\n",
|
169 |
+
" \"cashPerSharePerShareAnnual\": 3.9585,\n",
|
170 |
+
" \"cashPerSharePerShareQuarterly\": 4.3781,\n",
|
171 |
+
" \"currentDividendYieldTTM\": 0.5173,\n",
|
172 |
+
" \"currentEv/freeCashFlowAnnual\": 30.1134,\n",
|
173 |
+
" \"currentEv/freeCashFlowTTM\": 29.4235,\n",
|
174 |
+
" \"currentRatioAnnual\": 0.988,\n",
|
175 |
+
" \"currentRatioQuarterly\": 1.0371,\n",
|
176 |
+
" \"dividendGrowthRate5Y\": 6.58,\n",
|
177 |
+
" \"dividendPerShareAnnual\": 0.9542,\n",
|
178 |
+
" \"dividendPerShareTTM\": 0.975,\n",
|
179 |
+
" \"dividendYieldIndicatedAnnual\": 0.52383447,\n",
|
180 |
+
" \"ebitdPerShareAnnual\": 7.9328,\n",
|
181 |
+
" \"ebitdPerShareTTM\": 8.2919,\n",
|
182 |
+
" \"ebitdaCagr5Y\": 9.04,\n",
|
183 |
+
" \"ebitdaInterimCagr5Y\": 13.39,\n",
|
184 |
+
" \"enterpriseValue\": 2998809.5,\n",
|
185 |
+
" \"epsAnnual\": 6.1341,\n",
|
186 |
+
" \"epsBasicExclExtraItemsAnnual\": 6.1341,\n",
|
187 |
+
" \"epsBasicExclExtraItemsTTM\": 6.4308,\n",
|
188 |
+
" \"epsExclExtraItemsAnnual\": 6.1341,\n",
|
189 |
+
" \"epsExclExtraItemsTTM\": 6.4308,\n",
|
190 |
+
" \"epsGrowth3Y\": 23.26,\n",
|
191 |
+
" \"epsGrowth5Y\": 15.56,\n",
|
192 |
+
" \"epsGrowthQuarterlyYoy\": 0.25,\n",
|
193 |
+
" \"epsGrowthTTMYoy\": 9.26,\n",
|
194 |
+
" \"epsInclExtraItemsAnnual\": 6.1341,\n",
|
195 |
+
" \"epsInclExtraItemsTTM\": 6.4308,\n",
|
196 |
+
" \"epsNormalizedAnnual\": 6.1341,\n",
|
197 |
+
" \"epsTTM\": 6.4308,\n",
|
198 |
+
" \"focfCagr5Y\": 9.2,\n",
|
199 |
+
" \"grossMargin5Y\": 41.05,\n",
|
200 |
+
" \"grossMarginAnnual\": 44.13,\n",
|
201 |
+
" \"grossMarginTTM\": 46.58,\n",
|
202 |
+
" \"inventoryTurnoverAnnual\": 37.9777,\n",
|
203 |
+
" \"inventoryTurnoverTTM\": 30.2839,\n",
|
204 |
+
" \"longTermDebt/equityAnnual\": 1.5332,\n",
|
205 |
+
" \"longTermDebt/equityQuarterly\": 1.2377,\n",
|
206 |
+
" \"marketCapitalization\": 2926914.5,\n",
|
207 |
+
" \"monthToDatePriceReturnDaily\": 12.0766,\n",
|
208 |
+
" \"netIncomeEmployeeAnnual\": 0.6025,\n",
|
209 |
+
" \"netIncomeEmployeeTTM\": 0.6235,\n",
|
210 |
+
" \"netInterestCoverageAnnual\": 622.5082,\n",
|
211 |
+
" \"netInterestCoverageTTM\": 622.5082,\n",
|
212 |
+
" \"netMarginGrowth5Y\": 2.46,\n",
|
213 |
+
" \"netProfitMargin5Y\": 23.73,\n",
|
214 |
+
" \"netProfitMarginAnnual\": 25.31,\n",
|
215 |
+
" \"netProfitMarginTTM\": 26.04,\n",
|
216 |
+
" \"operatingMargin5Y\": 27.72,\n",
|
217 |
+
" \"operatingMarginAnnual\": 29.82,\n",
|
218 |
+
" \"operatingMarginTTM\": 30.74,\n",
|
219 |
+
" \"payoutRatioAnnual\": 15.49,\n",
|
220 |
+
" \"payoutRatioTTM\": 0.1508,\n",
|
221 |
+
" \"pbAnnual\": 43.4754,\n",
|
222 |
+
" \"pbQuarterly\": 35.6899,\n",
|
223 |
+
" \"pcfShareAnnual\": 26.4776,\n",
|
224 |
+
" \"pcfShareTTM\": 26.4728,\n",
|
225 |
+
" \"peAnnual\": 30.1759,\n",
|
226 |
+
" \"peBasicExclExtraTTM\": 29.1557,\n",
|
227 |
+
" \"peExclExtraAnnual\": 30.96975,\n",
|
228 |
+
" \"peExclExtraTTM\": 29.1557,\n",
|
229 |
+
" \"peInclExtraTTM\": 29.1557,\n",
|
230 |
+
" \"peNormalizedAnnual\": 30.1759,\n",
|
231 |
+
" \"peTTM\": 26.3771,\n",
|
232 |
+
" \"pfcfShareAnnual\": 29.3914,\n",
|
233 |
+
" \"pfcfShareTTM\": 28.718,\n",
|
234 |
+
" \"pretaxMargin5Y\": 27.89,\n",
|
235 |
+
" \"pretaxMarginAnnual\": 29.67,\n",
|
236 |
+
" \"pretaxMarginTTM\": 30.92,\n",
|
237 |
+
" \"priceRelativeToS&P50013Week\": -1.8546,\n",
|
238 |
+
" \"priceRelativeToS&P50026Week\": -16.2866,\n",
|
239 |
+
" \"priceRelativeToS&P5004Week\": 7.1353,\n",
|
240 |
+
" \"priceRelativeToS&P50052Week\": -16.6774,\n",
|
241 |
+
" \"priceRelativeToS&P500Ytd\": -12.317,\n",
|
242 |
+
" \"psAnnual\": 7.6364,\n",
|
243 |
+
" \"psTTM\": 6.9387,\n",
|
244 |
+
" \"ptbvAnnual\": 4.8643,\n",
|
245 |
+
" \"ptbvQuarterly\": 6.4066,\n",
|
246 |
+
" \"quickRatioAnnual\": 0.9444,\n",
|
247 |
+
" \"quickRatioQuarterly\": 0.9868,\n",
|
248 |
+
" \"receivablesTurnoverAnnual\": 13.2873,\n",
|
249 |
+
" \"receivablesTurnoverTTM\": 19.1901,\n",
|
250 |
+
" \"revenueEmployeeAnnual\": 2.3807,\n",
|
251 |
+
" \"revenueEmployeeTTM\": 2.3703,\n",
|
252 |
+
" \"revenueGrowth3Y\": 11.77,\n",
|
253 |
+
" \"revenueGrowth5Y\": 7.61,\n",
|
254 |
+
" \"revenueGrowthQuarterlyYoy\": -4.31,\n",
|
255 |
+
" \"revenueGrowthTTMYoy\": -0.9,\n",
|
256 |
+
" \"revenuePerShareAnnual\": 24.2393,\n",
|
257 |
+
" \"revenuePerShareTTM\": 24.677,\n",
|
258 |
+
" \"revenueShareGrowth5Y\": 12.79,\n",
|
259 |
+
" \"roa5Y\": 23.36,\n",
|
260 |
+
" \"roaRfy\": 27.51,\n",
|
261 |
+
" \"roaTTM\": 0.2913,\n",
|
262 |
+
" \"roe5Y\": 130.41,\n",
|
263 |
+
" \"roeRfy\": 156.07999999999998,\n",
|
264 |
+
" \"roeTTM\": 1.4833,\n",
|
265 |
+
" \"roi5Y\": 44.8,\n",
|
266 |
+
" \"roiAnnual\": 55.66,\n",
|
267 |
+
" \"roiTTM\": 57.06,\n",
|
268 |
+
" \"tangibleBookValuePerShareAnnual\": 5.8583,\n",
|
269 |
+
" \"tangibleBookValuePerShareQuarterly\": 6.7916,\n",
|
270 |
+
" \"tbvCagr5Y\": 11.34,\n",
|
271 |
+
" \"totalDebt/totalEquityAnnual\": 1.804,\n",
|
272 |
+
" \"totalDebt/totalEquityQuarterly\": 1.4097,\n",
|
273 |
+
" \"yearToDatePriceReturnDaily\": -0.8466,\n",
|
274 |
+
" \"bookValue\": 74194,\n",
|
275 |
+
" \"cashRatio\": 0.2640483920466476,\n",
|
276 |
+
" \"currentRatio\": 1.0371,\n",
|
277 |
+
" \"ebitPerShare\": 1.8041,\n",
|
278 |
+
" \"eps\": 1.5284,\n",
|
279 |
+
" \"ev\": 2719868.8,\n",
|
280 |
+
" \"fcfMargin\": 0.228,\n",
|
281 |
+
" \"fcfPerShareTTM\": 6.645,\n",
|
282 |
+
" \"grossMargin\": 0.4658,\n",
|
283 |
+
" \"longtermDebtTotalAsset\": 0.2722,\n",
|
284 |
+
" \"longtermDebtTotalCapital\": 0.5136,\n",
|
285 |
+
" \"longtermDebtTotalEquity\": 1.2377,\n",
|
286 |
+
" \"netDebtToTotalCapital\": 0.4021,\n",
|
287 |
+
" \"netDebtToTotalEquity\": 0.969,\n",
|
288 |
+
" \"netMargin\": 0.2604,\n",
|
289 |
+
" \"operatingMargin\": 0.3074,\n",
|
290 |
+
" \"pb\": 35.6899,\n",
|
291 |
+
" \"pfcfTTM\": 25.9812,\n",
|
292 |
+
" \"pretaxMargin\": 0.3092,\n",
|
293 |
+
" \"ptbv\": 6.4066,\n",
|
294 |
+
" \"quickRatio\": 0.9868,\n",
|
295 |
+
" \"roicTTM\": 0.5706,\n",
|
296 |
+
" \"rotcTTM\": 0.6721,\n",
|
297 |
+
" \"salesPerShare\": 5.8684,\n",
|
298 |
+
" \"sgaToSale\": 0.5342,\n",
|
299 |
+
" \"tangibleBookValue\": 138050,\n",
|
300 |
+
" \"totalDebtToEquity\": 1.4097,\n",
|
301 |
+
" \"totalDebtToTotalAsset\": 0.31,\n",
|
302 |
+
" \"totalDebtToTotalCapital\": 0.585,\n",
|
303 |
+
" \"totalRatio\": 1.2819\n",
|
304 |
+
"}\n",
|
305 |
+
"\u001b[32m**********************************************************************\u001b[0m\n",
|
306 |
+
"\n",
|
307 |
+
"--------------------------------------------------------------------------------\n",
|
308 |
+
"\u001b[33mUser_Proxy\u001b[0m (to Market_Analyst):\n",
|
309 |
+
"\n",
|
310 |
+
"\u001b[32m***** Response from calling tool (call_GPCnZaJrmUixSgPp5x9rgHs0) *****\u001b[0m\n",
|
311 |
+
" Open High Low Close Volume Dividends Stock Splits\n",
|
312 |
+
"Date \n",
|
313 |
+
"2024-05-16 00:00:00-04:00 190.470001 191.100006 189.660004 189.839996 52845200 0.0 0.0\n",
|
314 |
+
"2024-05-17 00:00:00-04:00 189.509995 190.809998 189.179993 189.869995 41282900 0.0 0.0\n",
|
315 |
+
"2024-05-20 00:00:00-04:00 189.330002 191.919998 189.009995 191.039993 44361300 0.0 0.0\n",
|
316 |
+
"2024-05-21 00:00:00-04:00 191.089996 192.729996 190.919998 192.350006 42309400 0.0 0.0\n",
|
317 |
+
"2024-05-22 00:00:00-04:00 192.270004 192.820007 190.270004 190.899994 34581900 0.0 0.0\n",
|
318 |
+
"\u001b[32m**********************************************************************\u001b[0m\n",
|
319 |
+
"\n",
|
320 |
+
"--------------------------------------------------------------------------------\n",
|
321 |
+
"\u001b[33mMarket_Analyst\u001b[0m (to User_Proxy):\n",
|
322 |
+
"\n",
|
323 |
+
"### Positive Developments for Apple\n",
|
324 |
+
"\n",
|
325 |
+
"1. **Share Buyback Announcement**: Apple's announcement of a $110 billion share buyback program is a strong signal of confidence from the management in the company's future prospects. This move is likely to support the stock price by reducing the number of shares outstanding, potentially increasing earnings per share (EPS) over time.\n",
|
326 |
+
"\n",
|
327 |
+
"2. **Financial Performance and Stability**: Apple's financial basics show a robust picture with a gross margin of 46.58% and an operating margin of 30.74%, indicating efficient operations and strong profitability. The company's EPS growth over the past year (9.26%) and a dividend growth rate of 6.58% over the past five years reflect its ability to generate and return value to shareholders.\n",
|
328 |
+
"\n",
|
329 |
+
"### Potential Concerns\n",
|
330 |
+
"\n",
|
331 |
+
"1. **Market Competition**: The news highlights other companies and technologies potentially challenging Apple's market position. For instance, Microsoft's development of tools that turn anyone into a software developer could democratize app development, potentially impacting Apple's ecosystem advantage.\n",
|
332 |
+
"\n",
|
333 |
+
"2. **Investor Sentiment and Market Dynamics**: The comparison of Apple's stock to another company that soared 28% over the last year following a buyback suggests that investors might be looking for quicker gains elsewhere. This could lead to volatility in Apple's stock price if investors perceive better opportunities in other stocks.\n",
|
334 |
+
"\n",
|
335 |
+
"### Stock Price Movement Prediction for Next Week\n",
|
336 |
+
"\n",
|
337 |
+
"Given the recent share buyback announcement and Apple's strong financial performance, there is a positive outlook for the stock. However, the broader market dynamics and competition could introduce some volatility. Considering the 5-day price return of 0.5584% and the overall positive sentiment, **Apple's stock price is likely to experience a slight uptick next week, potentially increasing by 1-2%**. This prediction is based on the company's solid financials, strategic share buyback, and the general market conditions reflected in the recent stock performance.\n",
|
338 |
+
"\n",
|
339 |
+
"### Summary Analysis\n",
|
340 |
+
"\n",
|
341 |
+
"Apple's recent share buyback announcement and its strong financial metrics, including high gross and operating margins, suggest a robust outlook for the company. Despite potential concerns regarding market competition and investor sentiment towards quicker gains, Apple's fundamental strength and strategic moves position it well for short-term growth. The slight increase in stock price over the past week, coupled with the company's overall performance, supports a cautiously optimistic prediction for the stock's movement in the coming week.\n",
|
342 |
+
"\n",
|
343 |
+
"--------------------------------------------------------------------------------\n",
|
344 |
+
"\u001b[33mUser_Proxy\u001b[0m (to Market_Analyst):\n",
|
345 |
+
"\n",
|
346 |
+
"\n",
|
347 |
+
"\n",
|
348 |
+
"--------------------------------------------------------------------------------\n",
|
349 |
+
"\u001b[33mMarket_Analyst\u001b[0m (to User_Proxy):\n",
|
350 |
+
"\n",
|
351 |
+
"TERMINATE\n",
|
352 |
+
"\n",
|
353 |
+
"--------------------------------------------------------------------------------\n"
|
354 |
+
]
|
355 |
+
}
|
356 |
+
],
|
357 |
+
"source": [
|
358 |
+
"company = \"APPLE\"\n",
|
359 |
+
"\n",
|
360 |
+
"assitant = SingleAssistant(\n",
|
361 |
+
" \"Market_Analyst\",\n",
|
362 |
+
" llm_config,\n",
|
363 |
+
" # set to \"ALWAYS\" if you want to chat instead of simply receiving the prediciton\n",
|
364 |
+
" human_input_mode=\"NEVER\",\n",
|
365 |
+
")\n",
|
366 |
+
"assitant.chat(\n",
|
367 |
+
" f\"Use all the tools provided to retrieve information available for {company} upon {get_current_date()}. Analyze the positive developments and potential concerns of {company} \"\n",
|
368 |
+
" \"with 2-4 most important factors respectively and keep them concise. Most factors should be inferred from company related news. \"\n",
|
369 |
+
" f\"Then make a rough prediction (e.g. up/down by 2-3%) of the {company} stock price movement for next week. Provide a summary analysis to support your prediction.\"\n",
|
370 |
+
")"
|
371 |
+
]
|
372 |
+
}
|
373 |
+
],
|
374 |
+
"metadata": {
|
375 |
+
"kernelspec": {
|
376 |
+
"display_name": "finrobot",
|
377 |
+
"language": "python",
|
378 |
+
"name": "python3"
|
379 |
+
},
|
380 |
+
"language_info": {
|
381 |
+
"codemirror_mode": {
|
382 |
+
"name": "ipython",
|
383 |
+
"version": 3
|
384 |
+
},
|
385 |
+
"file_extension": ".py",
|
386 |
+
"mimetype": "text/x-python",
|
387 |
+
"name": "python",
|
388 |
+
"nbconvert_exporter": "python",
|
389 |
+
"pygments_lexer": "ipython3",
|
390 |
+
"version": "3.10.14"
|
391 |
+
}
|
392 |
+
},
|
393 |
+
"nbformat": 4,
|
394 |
+
"nbformat_minor": 2
|
395 |
+
}
|
tutorials_beginner/agent_rag_qa.ipynb
ADDED
The diff for this file is too large to render.
See raw diff
|
|