├── .chainlit └── config.toml ├── .env.example ├── .gitignore ├── LICENSE ├── README.md ├── app.py ├── chainlit.md ├── create_assistant.py ├── public ├── idea.svg ├── learn.svg ├── terminal.svg └── write.svg ├── render.yaml ├── requirements.txt └── tesla-stock-price.csv /.chainlit/config.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | # Whether to enable telemetry (default: true). No personal data is collected. 3 | enable_telemetry = true 4 | 5 | 6 | # List of environment variables to be provided by each user to use the app. 7 | user_env = [] 8 | 9 | # Duration (in seconds) during which the session is saved when the connection is lost 10 | session_timeout = 3600 11 | 12 | # Enable third parties caching (e.g LangChain cache) 13 | cache = false 14 | 15 | # Authorized origins 16 | allow_origins = ["*"] 17 | 18 | # Follow symlink for asset mount (see https://github.com/Chainlit/chainlit/issues/317) 19 | # follow_symlink = false 20 | 21 | [features] 22 | # Show the prompt playground 23 | prompt_playground = true 24 | 25 | # Process and display HTML in messages. This can be a security risk (see https://stackoverflow.com/questions/19603097/why-is-it-dangerous-to-render-user-generated-html-or-javascript) 26 | unsafe_allow_html = false 27 | 28 | # Process and display mathematical expressions. This can clash with "$" characters in messages. 29 | latex = false 30 | 31 | # Automatically tag threads with the current chat profile (if a chat profile is used) 32 | auto_tag_thread = true 33 | 34 | # Authorize users to spontaneously upload files with messages 35 | [features.spontaneous_file_upload] 36 | enabled = true 37 | accept = ["*/*"] 38 | max_files = 20 39 | max_size_mb = 500 40 | 41 | [features.audio] 42 | # Threshold for audio recording 43 | min_decibels = -45 44 | # Delay for the user to start speaking in MS 45 | initial_silence_timeout = 3000 46 | # Delay for the user to continue speaking in MS. If the user stops speaking for this duration, the recording will stop. 47 | silence_timeout = 1500 48 | # Above this duration (MS), the recording will forcefully stop. 49 | max_duration = 15000 50 | # Duration of the audio chunks in MS 51 | chunk_duration = 1000 52 | # Sample rate of the audio 53 | sample_rate = 44100 54 | 55 | [UI] 56 | # Name of the app and chatbot. 57 | name = "Data Analyst" 58 | 59 | # Show the readme while the thread is empty. 60 | show_readme_as_default = false 61 | 62 | # Description of the app and chatbot. This is used for HTML tags. 63 | # description = "" 64 | 65 | # Large size content are by default collapsed for a cleaner ui 66 | default_collapse_content = true 67 | 68 | # The default value for the expand messages settings. 69 | default_expand_messages = false 70 | 71 | # Hide the chain of thought details from the user in the UI. 72 | hide_cot = false 73 | 74 | # Link to your github repo. This will add a github button in the UI's header. 75 | github = "https://github.com/willydouhard/data-analyst" 76 | 77 | # Specify a CSS file that can be used to customize the user interface. 78 | # The CSS file can be served from the public directory or via an external link. 79 | # custom_css = "/public/test.css" 80 | 81 | # Specify a Javascript file that can be used to customize the user interface. 82 | # The Javascript file can be served from the public directory. 83 | # custom_js = "/public/test.js" 84 | 85 | # Specify a custom font url. 86 | # custom_font = "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" 87 | 88 | # Specify a custom build directory for the frontend. 89 | # This can be used to customize the frontend code. 90 | # Be careful: If this is a relative path, it should not start with a slash. 91 | # custom_build = "./public/build" 92 | 93 | [UI.theme] 94 | default = "light" 95 | #layout = "wide" 96 | #font_family = "Inter, sans-serif" 97 | # Override default MUI light theme. (Check theme.ts) 98 | [UI.theme.light] 99 | #background = "#FAFAFA" 100 | #paper = "#FFFFFF" 101 | 102 | [UI.theme.light.primary] 103 | #main = "#F80061" 104 | #dark = "#980039" 105 | #light = "#FFE7EB" 106 | 107 | # Override default MUI dark theme. (Check theme.ts) 108 | [UI.theme.dark] 109 | #background = "#FAFAFA" 110 | #paper = "#FFFFFF" 111 | 112 | [UI.theme.dark.primary] 113 | #main = "#F80061" 114 | #dark = "#980039" 115 | #light = "#FFE7EB" 116 | 117 | 118 | [meta] 119 | generated_by = "1.1.0rc1" 120 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY= 2 | OPENAI_ASSISTANT_ID= 3 | LITERAL_API_KEY= 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | translations 3 | .files 4 | __pycache__ 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 chainlit 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Data Analyst with OpenAI Assistant 2 | 3 | ### Supported Assistant Features 4 | 5 | | Streaming | Files | Code Interpreter | File Search | Voice | 6 | | --------- | ----- | ---------------- | ----------- | ----- | 7 | | ✅ | ✅ | ✅ | ✅ | ✅ | 8 | 9 | ### Get an OpenAI API key 10 | 11 | Go to OpenAI's [API keys page](https://platform.openai.com/api-keys) and create one if you don't have one already. 12 | 13 | ### Create a .env file 14 | 15 | Copy the `.env.example` file to create your own `.env` file and set your `OPENAI_API_KEY`. 16 | 17 | ### Create the assistant 18 | 19 | `python create_assistant.py` 20 | 21 | This will print the id of your assistant, set it in your `.env` file. 22 | 23 | ### Run locally 24 | 25 | `chainlit run app.py` 26 | 27 | ### [Optional] Get a Literal AI API key 28 | 29 | > [!NOTE] 30 | > Literal AI is an all in one observability, evaluation and analytics platform for building LLM apps. 31 | 32 | Go to [Literal AI](https://cloud.getliteral.ai/), create a project and go to Settings to get your API key. 33 | 34 | ### Deploy 35 | 36 | Click on the button below, then set the API keys in the form and click on `Apply`. 37 | 38 | [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy) 39 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | import os 2 | import plotly 3 | from io import BytesIO 4 | from pathlib import Path 5 | from typing import List 6 | 7 | from openai import AsyncAssistantEventHandler, AsyncOpenAI, OpenAI 8 | 9 | from literalai.helper import utc_now 10 | 11 | import chainlit as cl 12 | from chainlit.config import config 13 | from chainlit.element import Element 14 | from openai.types.beta.threads.runs import RunStep 15 | 16 | 17 | async_openai_client = AsyncOpenAI(api_key=os.environ.get("OPENAI_API_KEY")) 18 | sync_openai_client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY")) 19 | 20 | assistant = sync_openai_client.beta.assistants.retrieve( 21 | os.environ.get("OPENAI_ASSISTANT_ID") 22 | ) 23 | 24 | config.ui.name = assistant.name 25 | 26 | class EventHandler(AsyncAssistantEventHandler): 27 | 28 | def __init__(self, assistant_name: str) -> None: 29 | super().__init__() 30 | self.current_message: cl.Message = None 31 | self.current_step: cl.Step = None 32 | self.current_tool_call = None 33 | self.assistant_name = assistant_name 34 | 35 | async def on_run_step_created(self, run_step: RunStep) -> None: 36 | cl.user_session.set("run_step", run_step) 37 | 38 | async def on_text_created(self, text) -> None: 39 | self.current_message = await cl.Message(author=self.assistant_name, content="").send() 40 | 41 | async def on_text_delta(self, delta, snapshot): 42 | if delta.value: 43 | await self.current_message.stream_token(delta.value) 44 | 45 | async def on_text_done(self, text): 46 | await self.current_message.update() 47 | if text.annotations: 48 | print(text.annotations) 49 | for annotation in text.annotations: 50 | if annotation.type == "file_path": 51 | response = await async_openai_client.files.with_raw_response.content(annotation.file_path.file_id) 52 | file_name = annotation.text.split("/")[-1] 53 | try: 54 | fig = plotly.io.from_json(response.content) 55 | element = cl.Plotly(name=file_name, figure=fig) 56 | await cl.Message( 57 | content="", 58 | elements=[element]).send() 59 | except Exception as e: 60 | element = cl.File(content=response.content, name=file_name) 61 | await cl.Message( 62 | content="", 63 | elements=[element]).send() 64 | # Hack to fix links 65 | if annotation.text in self.current_message.content and element.chainlit_key: 66 | self.current_message.content = self.current_message.content.replace(annotation.text, f"/project/file/{element.chainlit_key}?session_id={cl.context.session.id}") 67 | await self.current_message.update() 68 | 69 | async def on_tool_call_created(self, tool_call): 70 | self.current_tool_call = tool_call.id 71 | self.current_step = cl.Step(name=tool_call.type, type="tool") 72 | self.current_step.show_input = "python" 73 | self.current_step.start = utc_now() 74 | await self.current_step.send() 75 | 76 | async def on_tool_call_delta(self, delta, snapshot): 77 | if snapshot.id != self.current_tool_call: 78 | self.current_tool_call = snapshot.id 79 | self.current_step = cl.Step(name=delta.type, type="tool") 80 | self.current_step.start = utc_now() 81 | if snapshot.type == "code_interpreter": 82 | self.current_step.show_input = "python" 83 | if snapshot.type == "function": 84 | self.current_step.name = snapshot.function.name 85 | self.current_step.language = "json" 86 | await self.current_step.send() 87 | 88 | if delta.type == "function": 89 | pass 90 | 91 | if delta.type == "code_interpreter": 92 | if delta.code_interpreter.outputs: 93 | for output in delta.code_interpreter.outputs: 94 | if output.type == "logs": 95 | self.current_step.output += output.logs 96 | self.current_step.language = "markdown" 97 | self.current_step.end = utc_now() 98 | await self.current_step.update() 99 | elif output.type == "image": 100 | self.current_step.language = "json" 101 | self.current_step.output = output.image.model_dump_json() 102 | else: 103 | if delta.code_interpreter.input: 104 | await self.current_step.stream_token(delta.code_interpreter.input, is_input=True) 105 | 106 | async def on_event(self, event) -> None: 107 | if event.event == "error": 108 | return cl.ErrorMessage(content=str(event.data.message)).send() 109 | 110 | async def on_exception(self, exception: Exception) -> None: 111 | return cl.ErrorMessage(content=str(exception)).send() 112 | 113 | async def on_tool_call_done(self, tool_call): 114 | self.current_step.end = utc_now() 115 | await self.current_step.update() 116 | 117 | async def on_image_file_done(self, image_file, message): 118 | image_id = image_file.file_id 119 | response = await async_openai_client.files.with_raw_response.content(image_id) 120 | image_element = cl.Image( 121 | name=image_id, 122 | content=response.content, 123 | display="inline", 124 | size="large" 125 | ) 126 | if not self.current_message.elements: 127 | self.current_message.elements = [] 128 | self.current_message.elements.append(image_element) 129 | await self.current_message.update() 130 | 131 | 132 | @cl.step(type="tool") 133 | async def speech_to_text(audio_file): 134 | response = await async_openai_client.audio.transcriptions.create( 135 | model="whisper-1", file=audio_file 136 | ) 137 | 138 | return response.text 139 | 140 | 141 | async def upload_files(files: List[Element]): 142 | file_ids = [] 143 | for file in files: 144 | uploaded_file = await async_openai_client.files.create( 145 | file=Path(file.path), purpose="assistants" 146 | ) 147 | file_ids.append(uploaded_file.id) 148 | return file_ids 149 | 150 | 151 | async def process_files(files: List[Element]): 152 | # Upload files if any and get file_ids 153 | file_ids = [] 154 | if len(files) > 0: 155 | file_ids = await upload_files(files) 156 | 157 | return [ 158 | { 159 | "file_id": file_id, 160 | "tools": [{"type": "code_interpreter"}, {"type": "file_search"}] if file.mime in ["application/vnd.openxmlformats-officedocument.wordprocessingml.document", "text/markdown", "application/pdf", "text/plain"] else [{"type": "code_interpreter"}], 161 | } 162 | for file_id, file in zip(file_ids, files) 163 | ] 164 | 165 | 166 | @cl.set_starters 167 | async def set_starters(): 168 | return [ 169 | cl.Starter( 170 | label="Run Tesla stock analysis", 171 | message="Make a data analysis on the tesla-stock-price.csv file I previously uploaded.", 172 | icon="/public/write.svg", 173 | ), 174 | cl.Starter( 175 | label="Run a data analysis on my CSV", 176 | message="Make a data analysis on the next CSV file I will upload.", 177 | icon="/public/write.svg", 178 | ) 179 | ] 180 | 181 | @cl.on_chat_start 182 | async def start_chat(): 183 | # Create a Thread 184 | thread = await async_openai_client.beta.threads.create() 185 | # Store thread ID in user session for later use 186 | cl.user_session.set("thread_id", thread.id) 187 | 188 | 189 | @cl.on_stop 190 | async def stop_chat(): 191 | current_run_step: RunStep = cl.user_session.get("run_step") 192 | if current_run_step: 193 | await async_openai_client.beta.threads.runs.cancel(thread_id=current_run_step.thread_id, run_id=current_run_step.run_id) 194 | 195 | 196 | @cl.on_message 197 | async def main(message: cl.Message): 198 | thread_id = cl.user_session.get("thread_id") 199 | 200 | attachments = await process_files(message.elements) 201 | 202 | # Add a Message to the Thread 203 | oai_message = await async_openai_client.beta.threads.messages.create( 204 | thread_id=thread_id, 205 | role="user", 206 | content=message.content, 207 | attachments=attachments, 208 | ) 209 | 210 | # Create and Stream a Run 211 | async with async_openai_client.beta.threads.runs.stream( 212 | thread_id=thread_id, 213 | assistant_id=assistant.id, 214 | event_handler=EventHandler(assistant_name=assistant.name), 215 | ) as stream: 216 | await stream.until_done() 217 | 218 | 219 | @cl.on_audio_chunk 220 | async def on_audio_chunk(chunk: cl.AudioChunk): 221 | if chunk.isStart: 222 | buffer = BytesIO() 223 | # This is required for whisper to recognize the file type 224 | buffer.name = f"input_audio.{chunk.mimeType.split('/')[1]}" 225 | # Initialize the session for a new audio stream 226 | cl.user_session.set("audio_buffer", buffer) 227 | cl.user_session.set("audio_mime_type", chunk.mimeType) 228 | 229 | # Write the chunks to a buffer and transcribe the whole audio at the end 230 | cl.user_session.get("audio_buffer").write(chunk.data) 231 | 232 | 233 | @cl.on_audio_end 234 | async def on_audio_end(elements: list[Element]): 235 | # Get the audio buffer from the session 236 | audio_buffer: BytesIO = cl.user_session.get("audio_buffer") 237 | audio_buffer.seek(0) # Move the file pointer to the beginning 238 | audio_file = audio_buffer.read() 239 | audio_mime_type: str = cl.user_session.get("audio_mime_type") 240 | 241 | input_audio_el = cl.Audio( 242 | mime=audio_mime_type, content=audio_file, name=audio_buffer.name 243 | ) 244 | await cl.Message( 245 | type="user_message", 246 | content="", 247 | elements=[input_audio_el, *elements], 248 | ).send() 249 | 250 | whisper_input = (audio_buffer.name, audio_file, audio_mime_type) 251 | transcription = await speech_to_text(whisper_input) 252 | 253 | msg = cl.Message(author="You", content=transcription, elements=elements) 254 | 255 | await main(message=msg) 256 | -------------------------------------------------------------------------------- /chainlit.md: -------------------------------------------------------------------------------- 1 | # Welcome to Chainlit! 🚀🤖 2 | 3 | Hi there, Developer! 👋 We're excited to have you on board. Chainlit is a powerful tool designed to help you prototype, debug and share applications built on top of LLMs. 4 | 5 | ## Useful Links 🔗 6 | 7 | - **Documentation:** Get started with our comprehensive [Chainlit Documentation](https://docs.chainlit.io) 📚 8 | - **Discord Community:** Join our friendly [Chainlit Discord](https://discord.gg/k73SQ3FyUh) to ask questions, share your projects, and connect with other developers! 💬 9 | 10 | We can't wait to see what you create with Chainlit! Happy coding! 💻😊 11 | 12 | ## Welcome screen 13 | 14 | To modify the welcome screen, edit the `chainlit.md` file at the root of your project. If you do not want a welcome screen, just leave this file empty. 15 | -------------------------------------------------------------------------------- /create_assistant.py: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv 2 | load_dotenv() 3 | 4 | import os 5 | from openai import OpenAI 6 | 7 | openai_client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY")) 8 | 9 | 10 | instructions = """You are an assistant running data analysis on CSV files. 11 | 12 | You will use code interpreter to run the analysis. 13 | 14 | However, instead of rendering the charts as images, you will generate a plotly figure and turn it into json. 15 | You will create a file for each json that I can download through annotations. 16 | """ 17 | 18 | tools = [ 19 | {"type": "code_interpreter"}, 20 | {"type": "file_search"} 21 | ] 22 | 23 | file = openai_client.files.create( 24 | file=open("tesla-stock-price.csv", "rb"), 25 | purpose='assistants' 26 | ) 27 | 28 | 29 | assistant = openai_client.beta.assistants.create( 30 | model="gpt-4o", 31 | name="Data Analysis Assistant", 32 | instructions=instructions, 33 | temperature=0.1, 34 | tools=tools, 35 | tool_resources={ 36 | "code_interpreter": { 37 | "file_ids": [file.id] 38 | } 39 | } 40 | ) 41 | 42 | print(f"Assistant created with id: {assistant.id}") -------------------------------------------------------------------------------- /public/idea.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/learn.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/terminal.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/write.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /render.yaml: -------------------------------------------------------------------------------- 1 | # Exported from Render on 2024-05-20T20:56:35Z 2 | services: 3 | - type: web 4 | name: Chainlit-Data-Analyst 5 | runtime: python 6 | repo: https://github.com/willydouhard/data-analyst 7 | plan: starter 8 | envVars: 9 | - key: OPENAI_API_KEY 10 | sync: false 11 | - key: OPENAI_ASSISTANT_ID 12 | sync: false 13 | - key: LITERAL_API_KEY 14 | sync: false 15 | region: frankfurt 16 | buildCommand: pip install -r requirements.txt 17 | startCommand: chainlit run app.py -h --port $PORT 18 | version: "1" 19 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | openai 2 | plotly 3 | chainlit==1.1.300rc4 -------------------------------------------------------------------------------- /tesla-stock-price.csv: -------------------------------------------------------------------------------- 1 | Date,Open,High,Low,Close,Adj Close,Volume 2 | 2023-05-30,200.100006,204.479996,197.529999,201.160004,201.160004,128818700 3 | 2023-05-31,199.779999,203.949997,195.119995,203.929993,203.929993,150711700 4 | 2023-06-01,202.589996,209.800003,199.369995,207.520004,207.520004,148029900 5 | 2023-06-02,210.149994,217.250000,209.750000,213.970001,213.970001,164129000 6 | 2023-06-05,217.800003,221.289993,214.520004,217.610001,217.610001,151143100 7 | 2023-06-06,216.139999,221.910004,212.529999,221.309998,221.309998,146911600 8 | 2023-06-07,228.000000,230.830002,223.199997,224.570007,224.570007,185710800 9 | 2023-06-08,224.220001,235.229996,223.009995,234.860001,234.860001,164489700 10 | 2023-06-09,249.070007,252.419998,242.020004,244.399994,244.399994,199882300 11 | 2023-06-12,247.940002,250.970001,244.589996,249.830002,249.830002,150337900 12 | 2023-06-13,253.509995,259.679993,251.339996,258.709991,258.709991,162384300 13 | 2023-06-14,260.170013,261.570007,250.500000,256.790009,256.790009,170575500 14 | 2023-06-15,248.399994,258.950012,247.289993,255.899994,255.899994,160171200 15 | 2023-06-16,258.920013,263.600006,257.209991,260.540009,260.540009,167563700 16 | 2023-06-20,261.500000,274.750000,261.119995,274.450012,274.450012,165611200 17 | 2023-06-21,275.130005,276.989990,257.779999,259.459991,259.459991,211797100 18 | 2023-06-22,250.770004,265.000000,248.250000,264.609985,264.609985,166875900 19 | 2023-06-23,259.290009,262.450012,252.800003,256.600006,256.600006,176584100 20 | 2023-06-26,250.070007,258.369995,240.699997,241.050003,241.050003,179990600 21 | 2023-06-27,243.240005,250.389999,240.850006,250.210007,250.210007,164968200 22 | 2023-06-28,249.699997,259.880005,248.889999,256.239990,256.239990,159770800 23 | 2023-06-29,258.029999,260.739990,253.610001,257.500000,257.500000,131283400 24 | 2023-06-30,260.600006,264.450012,259.890015,261.769989,261.769989,112267600 25 | 2023-07-03,276.489990,284.250000,275.109985,279.820007,279.820007,119685900 26 | 2023-07-05,278.820007,283.850006,277.600006,282.480011,282.480011,131530900 27 | 2023-07-06,278.089996,279.970001,272.880005,276.540009,276.540009,120332100 28 | 2023-07-07,278.429993,280.779999,273.769989,274.429993,274.429993,113602000 29 | 2023-07-10,276.470001,277.519989,265.100006,269.609985,269.609985,119425400 30 | 2023-07-11,268.649994,270.899994,266.369995,269.790009,269.790009,91972400 31 | 2023-07-12,276.329987,276.519989,271.459991,271.989990,271.989990,95672100 32 | 2023-07-13,274.589996,279.450012,270.600006,277.899994,277.899994,112681500 33 | 2023-07-14,277.010010,285.299988,276.309998,281.380005,281.380005,119771100 34 | 2023-07-17,286.630005,292.230011,283.570007,290.380005,290.380005,131569600 35 | 2023-07-18,290.149994,295.260010,286.010010,293.339996,293.339996,112434700 36 | 2023-07-19,296.040009,299.290009,289.519989,291.260010,291.260010,142355400 37 | 2023-07-20,279.559998,280.929993,261.200012,262.899994,262.899994,175158300 38 | 2023-07-21,268.000000,268.000000,255.800003,260.019989,260.019989,161050100 39 | 2023-07-24,255.850006,269.850006,254.119995,269.059998,269.059998,136508500 40 | 2023-07-25,272.380005,272.899994,265.000000,265.279999,265.279999,112757300 41 | 2023-07-26,263.250000,268.040009,261.750000,264.350006,264.350006,95856200 42 | 2023-07-27,268.309998,269.130005,255.300003,255.710007,255.710007,103697300 43 | 2023-07-28,259.859985,267.250000,258.230011,266.440002,266.440002,111446000 44 | 2023-07-31,267.480011,269.079987,263.779999,267.429993,267.429993,84582200 45 | 2023-08-01,266.260010,266.470001,260.250000,261.070007,261.070007,83166000 46 | 2023-08-02,255.570007,259.519989,250.490005,254.110001,254.110001,101752900 47 | 2023-08-03,252.039993,260.489990,252.000000,259.320007,259.320007,97569100 48 | 2023-08-04,260.970001,264.769989,253.110001,253.860001,253.860001,99242600 49 | 2023-08-07,251.449997,253.649994,242.759995,251.449997,251.449997,111097900 50 | 2023-08-08,247.449997,250.919998,245.009995,249.699997,249.699997,96642200 51 | 2023-08-09,250.869995,251.100006,241.899994,242.190002,242.190002,101596300 52 | 2023-08-10,245.399994,251.800003,243.000000,245.339996,245.339996,109498600 53 | 2023-08-11,241.770004,243.789993,238.020004,242.649994,242.649994,98866600 54 | 2023-08-14,235.699997,240.660004,233.750000,239.759995,239.759995,98595300 55 | 2023-08-15,238.729996,240.500000,232.610001,232.960007,232.960007,88197600 56 | 2023-08-16,228.020004,233.970001,225.380005,225.600006,225.600006,112484500 57 | 2023-08-17,226.059998,226.740005,218.830002,219.220001,219.220001,120718400 58 | 2023-08-18,214.119995,217.580002,212.360001,215.490005,215.490005,135813700 59 | 2023-08-21,221.550003,232.130005,220.580002,231.279999,231.279999,135702700 60 | 2023-08-22,240.250000,240.820007,229.550003,233.190002,233.190002,130597900 61 | 2023-08-23,229.339996,238.979996,229.289993,236.860001,236.860001,101077600 62 | 2023-08-24,238.660004,238.919998,228.179993,230.039993,230.039993,99777400 63 | 2023-08-25,231.309998,239.000000,230.350006,238.589996,238.589996,106612200 64 | 2023-08-28,242.580002,244.380005,235.350006,238.820007,238.820007,107673700 65 | 2023-08-29,238.580002,257.480011,237.770004,257.179993,257.179993,134047600 66 | 2023-08-30,254.199997,260.510010,250.589996,256.899994,256.899994,121988400 67 | 2023-08-31,255.979996,261.179993,255.050003,258.079987,258.079987,108861700 68 | 2023-09-01,257.260010,259.079987,242.009995,245.009995,245.009995,132272500 69 | 2023-09-05,245.000000,258.000000,244.860001,256.489990,256.489990,129469600 70 | 2023-09-06,255.139999,255.389999,245.059998,251.919998,251.919998,116959800 71 | 2023-09-07,245.070007,252.809998,243.270004,251.490005,251.490005,115312900 72 | 2023-09-08,251.220001,256.519989,246.669998,248.500000,248.500000,118367700 73 | 2023-09-11,264.269989,274.850006,260.609985,273.579987,273.579987,174667900 74 | 2023-09-12,270.760010,278.390015,266.600006,267.480011,267.480011,135999900 75 | 2023-09-13,270.070007,274.980011,268.100006,271.299988,271.299988,111673700 76 | 2023-09-14,271.320007,276.709991,270.420013,276.040009,276.040009,107709800 77 | 2023-09-15,277.549988,278.980011,271.000000,274.390015,274.390015,133422800 78 | 2023-09-18,271.160004,271.440002,263.760010,265.279999,265.279999,101543300 79 | 2023-09-19,264.350006,267.850006,261.200012,266.500000,266.500000,103704000 80 | 2023-09-20,267.040009,273.929993,262.459991,262.589996,262.589996,122514600 81 | 2023-09-21,257.850006,260.859985,254.210007,255.699997,255.699997,119531000 82 | 2023-09-22,257.399994,257.790009,244.479996,244.880005,244.880005,127524100 83 | 2023-09-25,243.380005,247.100006,238.309998,246.990005,246.990005,104636600 84 | 2023-09-26,242.979996,249.550003,241.660004,244.119995,244.119995,101993600 85 | 2023-09-27,244.259995,245.330002,234.580002,240.500000,240.500000,136597200 86 | 2023-09-28,240.020004,247.550003,238.649994,246.380005,246.380005,117058900 87 | 2023-09-29,250.000000,254.770004,246.350006,250.220001,250.220001,128346200 88 | 2023-10-02,244.809998,254.279999,242.619995,251.600006,251.600006,123810400 89 | 2023-10-03,248.610001,250.020004,244.449997,246.529999,246.529999,101985300 90 | 2023-10-04,248.139999,261.859985,247.600006,261.160004,261.160004,129721600 91 | 2023-10-05,260.000000,263.600006,256.250000,260.049988,260.049988,119159200 92 | 2023-10-06,253.979996,261.649994,250.649994,260.529999,260.529999,117947000 93 | 2023-10-09,255.309998,261.359985,252.050003,259.670013,259.670013,101377900 94 | 2023-10-10,257.750000,268.940002,257.649994,263.619995,263.619995,122656000 95 | 2023-10-11,266.200012,268.600006,260.899994,262.989990,262.989990,103706300 96 | 2023-10-12,262.920013,265.410004,256.630005,258.869995,258.869995,111508100 97 | 2023-10-13,258.899994,259.600006,250.220001,251.119995,251.119995,102073800 98 | 2023-10-16,250.050003,255.399994,248.479996,253.919998,253.919998,88917200 99 | 2023-10-17,250.100006,257.179993,247.080002,254.850006,254.850006,93562900 100 | 2023-10-18,252.699997,254.630005,242.080002,242.679993,242.679993,125147800 101 | 2023-10-19,225.949997,230.610001,216.779999,220.110001,220.110001,170772700 102 | 2023-10-20,217.009995,218.860001,210.419998,211.990005,211.990005,137734000 103 | 2023-10-23,210.000000,216.979996,202.509995,212.080002,212.080002,150683400 104 | 2023-10-24,216.500000,222.050003,214.110001,216.520004,216.520004,118231100 105 | 2023-10-25,215.880005,220.100006,212.199997,212.419998,212.419998,107065100 106 | 2023-10-26,211.320007,214.800003,204.880005,205.759995,205.759995,115112600 107 | 2023-10-27,210.600006,212.410004,205.770004,207.300003,207.300003,94881200 108 | 2023-10-30,209.279999,210.880005,194.669998,197.360001,197.360001,136448200 109 | 2023-10-31,196.119995,202.800003,194.070007,200.839996,200.839996,118068300 110 | 2023-11-01,204.039993,205.990005,197.850006,205.660004,205.660004,121661700 111 | 2023-11-02,212.970001,219.199997,211.449997,218.509995,218.509995,125987600 112 | 2023-11-03,221.149994,226.369995,218.399994,219.960007,219.960007,119281000 113 | 2023-11-06,223.979996,226.320007,215.000000,219.270004,219.270004,117335800 114 | 2023-11-07,219.979996,223.119995,215.720001,222.179993,222.179993,116900100 115 | 2023-11-08,223.149994,224.149994,217.639999,222.110001,222.110001,106584800 116 | 2023-11-09,219.750000,220.800003,206.679993,209.979996,209.979996,142110500 117 | 2023-11-10,210.029999,215.380005,205.690002,214.649994,214.649994,130994000 118 | 2023-11-13,215.600006,225.399994,211.610001,223.710007,223.710007,140447600 119 | 2023-11-14,235.029999,238.139999,230.720001,237.410004,237.410004,149771600 120 | 2023-11-15,239.289993,246.699997,236.449997,242.839996,242.839996,150354000 121 | 2023-11-16,239.490005,240.880005,230.960007,233.589996,233.589996,136816800 122 | 2023-11-17,232.000000,237.389999,226.539993,234.300003,234.300003,142532800 123 | 2023-11-20,234.039993,237.100006,231.020004,235.600006,235.600006,116320100 124 | 2023-11-21,235.039993,243.619995,233.339996,241.199997,241.199997,122288000 125 | 2023-11-22,242.039993,244.009995,231.399994,234.210007,234.210007,117950600 126 | 2023-11-24,233.750000,238.750000,232.330002,235.449997,235.449997,65125200 127 | 2023-11-27,236.889999,238.330002,232.100006,236.080002,236.080002,112031800 128 | 2023-11-28,236.679993,247.000000,234.009995,246.720001,246.720001,148549900 129 | 2023-11-29,249.210007,252.750000,242.759995,244.139999,244.139999,135401300 130 | 2023-11-30,245.139999,245.220001,236.910004,240.080002,240.080002,132353200 131 | 2023-12-01,233.139999,240.190002,231.899994,238.830002,238.830002,121173500 132 | 2023-12-04,235.750000,239.369995,233.289993,235.580002,235.580002,104099800 133 | 2023-12-05,233.869995,246.660004,233.699997,238.720001,238.720001,137971100 134 | 2023-12-06,242.919998,246.570007,239.169998,239.369995,239.369995,126436200 135 | 2023-12-07,241.550003,244.080002,236.979996,242.639999,242.639999,107142300 136 | 2023-12-08,240.270004,245.270004,239.270004,243.839996,243.839996,102980100 137 | 2023-12-11,242.740005,243.440002,237.449997,239.740005,239.740005,97913900 138 | 2023-12-12,238.550003,238.990005,233.869995,237.009995,237.009995,95328300 139 | 2023-12-13,234.190002,240.300003,228.199997,239.289993,239.289993,146286300 140 | 2023-12-14,241.220001,253.880005,240.789993,251.050003,251.050003,160829200 141 | 2023-12-15,251.210007,254.130005,248.300003,253.500000,253.500000,135720800 142 | 2023-12-18,253.779999,258.739990,251.360001,252.080002,252.080002,116416500 143 | 2023-12-19,253.479996,258.339996,253.009995,257.220001,257.220001,106737400 144 | 2023-12-20,256.410004,259.839996,247.000000,247.139999,247.139999,125097000 145 | 2023-12-21,251.899994,254.800003,248.550003,254.500000,254.500000,109594200 146 | 2023-12-22,256.760010,258.220001,251.369995,252.539993,252.539993,93249800 147 | 2023-12-26,254.490005,257.970001,252.910004,256.609985,256.609985,86892400 148 | 2023-12-27,258.350006,263.339996,257.519989,261.440002,261.440002,106494400 149 | 2023-12-28,263.660004,265.130005,252.710007,253.179993,253.179993,113619900 150 | 2023-12-29,255.100006,255.190002,247.429993,248.479996,248.479996,100615300 151 | 2024-01-02,250.080002,251.250000,244.410004,248.419998,248.419998,104654200 152 | 2024-01-03,244.979996,245.679993,236.320007,238.449997,238.449997,121082600 153 | 2024-01-04,239.250000,242.699997,237.729996,237.929993,237.929993,102629300 154 | 2024-01-05,236.860001,240.119995,234.899994,237.490005,237.490005,92379400 155 | 2024-01-08,236.139999,241.250000,235.300003,240.449997,240.449997,85166600 156 | 2024-01-09,238.110001,238.960007,232.039993,234.960007,234.960007,96705700 157 | 2024-01-10,235.100006,235.500000,231.289993,233.940002,233.940002,91628500 158 | 2024-01-11,230.570007,230.929993,225.369995,227.220001,227.220001,105873600 159 | 2024-01-12,220.080002,225.339996,217.149994,218.889999,218.889999,122889000 160 | 2024-01-16,215.100006,223.490005,212.179993,219.910004,219.910004,115355000 161 | 2024-01-17,214.860001,215.669998,212.009995,215.550003,215.550003,103164400 162 | 2024-01-18,216.880005,217.449997,208.740005,211.880005,211.880005,108595400 163 | 2024-01-19,209.990005,213.190002,207.559998,212.190002,212.190002,102095800 164 | 2024-01-22,212.259995,217.800003,206.270004,208.800003,208.800003,117952500 165 | 2024-01-23,211.300003,215.649994,207.750000,209.139999,209.139999,106605900 166 | 2024-01-24,211.880005,212.729996,206.770004,207.830002,207.830002,123369900 167 | 2024-01-25,189.699997,193.000000,180.059998,182.630005,182.630005,198076800 168 | 2024-01-26,185.500000,186.779999,182.100006,183.250000,183.250000,107343200 169 | 2024-01-29,185.630005,191.479996,183.669998,190.929993,190.929993,125013100 170 | 2024-01-30,195.330002,196.360001,190.610001,191.589996,191.589996,109982300 171 | 2024-01-31,187.000000,193.970001,185.850006,187.289993,187.289993,103221400 172 | 2024-02-01,188.500000,189.880005,184.279999,188.860001,188.860001,91843300 173 | 2024-02-02,185.039993,188.690002,182.000000,187.910004,187.910004,110505100 174 | 2024-02-05,184.259995,184.679993,175.009995,181.059998,181.059998,134294400 175 | 2024-02-06,177.210007,186.490005,177.110001,185.100006,185.100006,122676000 176 | 2024-02-07,188.179993,189.789993,182.679993,187.580002,187.580002,111535200 177 | 2024-02-08,189.000000,191.619995,185.580002,189.559998,189.559998,83034000 178 | 2024-02-09,190.179993,194.119995,189.479996,193.570007,193.570007,84476300 179 | 2024-02-12,192.110001,194.729996,187.279999,188.130005,188.130005,95498600 180 | 2024-02-13,183.990005,187.259995,182.110001,184.020004,184.020004,86759500 181 | 2024-02-14,185.300003,188.889999,183.350006,188.710007,188.710007,81203000 182 | 2024-02-15,189.160004,200.880005,188.860001,200.449997,200.449997,120831800 183 | 2024-02-16,202.059998,203.169998,197.399994,199.949997,199.949997,111173600 184 | 2024-02-20,196.130005,198.600006,189.130005,193.759995,193.759995,104545800 185 | 2024-02-21,193.360001,199.440002,191.949997,194.770004,194.770004,103844000 186 | 2024-02-22,194.000000,198.320007,191.360001,197.410004,197.410004,92739500 187 | 2024-02-23,195.309998,197.570007,191.500000,191.970001,191.970001,78841900 188 | 2024-02-26,192.289993,201.779999,192.000000,199.399994,199.399994,111747100 189 | 2024-02-27,204.039993,205.600006,198.259995,199.729996,199.729996,108645400 190 | 2024-02-28,200.419998,205.300003,198.440002,202.039993,202.039993,99806200 191 | 2024-02-29,204.179993,205.279999,198.449997,201.880005,201.880005,85907000 192 | 2024-03-01,200.520004,204.520004,198.500000,202.639999,202.639999,82099200 193 | 2024-03-04,198.729996,199.750000,186.720001,188.139999,188.139999,134334900 194 | 2024-03-05,183.050003,184.589996,177.570007,180.740005,180.740005,119660800 195 | 2024-03-06,179.990005,181.580002,173.699997,176.539993,176.539993,107920900 196 | 2024-03-07,174.350006,180.039993,173.699997,178.649994,178.649994,102129000 197 | 2024-03-08,181.500000,182.729996,174.699997,175.339996,175.339996,85315300 198 | 2024-03-11,175.449997,182.869995,174.800003,177.770004,177.770004,85391500 199 | 2024-03-12,177.770004,179.429993,172.410004,177.539993,177.539993,87391700 200 | 2024-03-13,173.050003,176.050003,169.149994,169.479996,169.479996,106524500 201 | 2024-03-14,167.770004,171.169998,160.509995,162.500000,162.500000,126325700 202 | 2024-03-15,163.160004,165.179993,160.759995,163.570007,163.570007,96971900 203 | 2024-03-18,170.020004,174.720001,165.899994,173.800003,173.800003,108214400 204 | 2024-03-19,172.360001,172.820007,167.419998,171.320007,171.320007,77271400 205 | 2024-03-20,173.000000,176.250000,170.820007,175.660004,175.660004,83846700 206 | 2024-03-21,176.389999,178.179993,171.800003,172.820007,172.820007,73178000 207 | 2024-03-22,166.690002,171.199997,166.300003,170.830002,170.830002,75454700 208 | 2024-03-25,168.759995,175.240005,168.729996,172.630005,172.630005,74228600 209 | 2024-03-26,178.580002,184.250000,177.380005,177.669998,177.669998,113186200 210 | 2024-03-27,181.410004,181.910004,176.000000,179.830002,179.830002,81804000 211 | 2024-03-28,177.449997,179.570007,175.300003,175.789993,175.789993,77654800 212 | 2024-04-01,176.169998,176.750000,170.210007,175.220001,175.220001,81562100 213 | 2024-04-02,164.750000,167.690002,163.429993,166.630005,166.630005,116650600 214 | 2024-04-03,164.020004,168.820007,163.279999,168.380005,168.380005,82950100 215 | 2024-04-04,170.070007,177.190002,168.009995,171.110001,171.110001,123162000 216 | 2024-04-05,169.080002,170.860001,160.509995,164.899994,164.899994,141250700 217 | 2024-04-08,169.339996,174.500000,167.789993,172.979996,172.979996,104423300 218 | 2024-04-09,172.910004,179.220001,171.919998,176.880005,176.880005,103232700 219 | 2024-04-10,173.039993,174.929993,170.009995,171.759995,171.759995,84532400 220 | 2024-04-11,172.550003,175.880005,168.509995,174.600006,174.600006,94516000 221 | 2024-04-12,172.339996,173.809998,170.360001,171.050003,171.050003,64506600 222 | 2024-04-15,170.240005,170.690002,161.380005,161.479996,161.479996,100245300 223 | 2024-04-16,156.740005,158.190002,153.750000,157.110001,157.110001,97000000 224 | 2024-04-17,157.639999,158.330002,153.779999,155.449997,155.449997,82439700 225 | 2024-04-18,151.250000,152.199997,148.699997,149.929993,149.929993,96098800 226 | 2024-04-19,148.970001,150.940002,146.220001,147.050003,147.050003,86005100 227 | 2024-04-22,140.559998,144.440002,138.800003,142.050003,142.050003,107097600 228 | 2024-04-23,143.330002,147.259995,141.110001,144.679993,144.679993,124545100 229 | 2024-04-24,162.839996,167.970001,157.509995,162.130005,162.130005,181178000 230 | 2024-04-25,158.960007,170.880005,158.360001,170.179993,170.179993,126427500 231 | 2024-04-26,168.850006,172.119995,166.369995,168.289993,168.289993,109815700 232 | 2024-04-29,188.419998,198.869995,184.539993,194.050003,194.050003,243869700 233 | 2024-04-30,186.979996,190.949997,182.839996,183.279999,183.279999,127031800 234 | 2024-05-01,182.000000,185.860001,179.009995,179.990005,179.990005,92829700 235 | 2024-05-02,182.860001,184.600006,176.020004,180.009995,180.009995,89148000 236 | 2024-05-03,182.100006,184.779999,178.419998,181.190002,181.190002,75491500 237 | 2024-05-06,183.800003,187.559998,182.199997,184.759995,184.759995,84390300 238 | 2024-05-07,182.399994,183.259995,177.399994,177.809998,177.809998,75045900 239 | 2024-05-08,171.589996,176.059998,170.149994,174.720001,174.720001,79969500 240 | 2024-05-09,175.009995,175.619995,171.369995,171.970001,171.970001,65950300 241 | 2024-05-10,173.050003,173.059998,167.750000,168.470001,168.470001,72627200 242 | 2024-05-13,170.000000,175.399994,169.000000,171.889999,171.889999,67018900 243 | 2024-05-14,174.500000,179.490005,174.070007,177.550003,177.550003,86407400 244 | 2024-05-15,179.899994,180.000000,173.110001,173.990005,173.990005,79663000 245 | 2024-05-16,174.100006,175.789993,171.429993,174.839996,174.839996,59812200 246 | 2024-05-17,173.550003,179.630005,172.750000,177.460007,177.460007,77445800 247 | 2024-05-20,177.559998,177.750000,173.520004,174.949997,174.949997,61727400 248 | 2024-05-21,175.509995,186.880005,174.710007,186.600006,186.600006,115266500 249 | 2024-05-22,182.850006,183.800003,178.119995,180.110001,180.110001,88313500 250 | 2024-05-23,181.800003,181.899994,173.259995,173.740005,173.740005,71975500 251 | 2024-05-24,174.839996,180.080002,173.729996,179.240005,179.240005,65479700 252 | 2024-05-28,176.399994,178.250000,173.160004,176.750000,176.750000,59736600 253 | 2024-05-29,174.190002,178.149994,173.929993,176.190002,176.190002,54684300 --------------------------------------------------------------------------------