├── .gitignore
├── 1. Thread Basics.ipynb
├── 2. Thread Data & Race Conditions.ipynb
├── 3. Deadlocks.ipynb
├── 4. Producer-Consumer model.ipynb
├── 5. The Python GIL.ipynb
├── 6. Multiprocessing.ipynb
├── 7. concurrent.futures.ipynb
├── 8. Parallel.ipynb
├── LICENSE
├── README.md
├── crypto-examples
├── Create Database.ipynb
├── Download Crypto Data.ipynb
├── Example - Thread.ipynb
├── Example - concurrent.futures.ipynb
├── Insert in Database.ipynb
├── crypto_data
│ ├── bitfinex_btc.csv
│ ├── bitfinex_eth.csv
│ ├── bitfinex_ltc.csv
│ ├── bitstamp_btc.csv
│ ├── bitstamp_eth.csv
│ ├── bitstamp_ltc.csv
│ ├── bittrex_btc.csv
│ ├── bittrex_eth.csv
│ ├── bittrex_ltc.csv
│ ├── cexio_btc.csv
│ ├── cexio_eth.csv
│ ├── cexio_ltc.csv
│ ├── coinbase-pro_btc.csv
│ ├── coinbase-pro_eth.csv
│ ├── coinbase-pro_ltc.csv
│ ├── hitbtc_btc.csv
│ ├── hitbtc_eth.csv
│ ├── hitbtc_ltc.csv
│ ├── huobi_btc.csv
│ ├── huobi_eth.csv
│ ├── huobi_ltc.csv
│ ├── kraken_btc.csv
│ ├── kraken_eth.csv
│ ├── kraken_ltc.csv
│ ├── mexbt_btc.csv
│ ├── okex_btc.csv
│ ├── okex_eth.csv
│ ├── okex_ltc.csv
│ ├── poloniex_btc.csv
│ ├── poloniex_eth.csv
│ └── poloniex_ltc.csv
├── flask_app.py
├── prices.db
└── templates
│ └── index.html
├── data
└── prime_mixture.txt
├── img
├── Deadlock_at_a_four-way-stop.gif
├── cpu_data.png
├── deadlocks.png
├── gil_meme.jpeg
├── multiple_process.png
├── parallel.png
├── producer-consumer-model.png
├── recording_studio_light.png
├── thistall.jpg
├── thread_shared_data.png
└── thread_states.png
├── poetry.lock
├── pyproject.toml
├── requirements.txt
└── sleep_n_seconds.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | .DS_Store
3 | .vscode
4 | __pycache__/
5 | *.py[cod]
6 | *$py.class
7 |
8 | # C extensions
9 | *.so
10 |
11 | # Distribution / packaging
12 | .Python
13 | build/
14 | develop-eggs/
15 | dist/
16 | downloads/
17 | eggs/
18 | .eggs/
19 | lib/
20 | lib64/
21 | parts/
22 | sdist/
23 | var/
24 | wheels/
25 | share/python-wheels/
26 | *.egg-info/
27 | .installed.cfg
28 | *.egg
29 | MANIFEST
30 |
31 | # PyInstaller
32 | # Usually these files are written by a python script from a template
33 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
34 | *.manifest
35 | *.spec
36 |
37 | # Installer logs
38 | pip-log.txt
39 | pip-delete-this-directory.txt
40 |
41 | # Unit test / coverage reports
42 | htmlcov/
43 | .tox/
44 | .nox/
45 | .coverage
46 | .coverage.*
47 | .cache
48 | nosetests.xml
49 | coverage.xml
50 | *.cover
51 | *.py,cover
52 | .hypothesis/
53 | .pytest_cache/
54 | cover/
55 |
56 | # Translations
57 | *.mo
58 | *.pot
59 |
60 | # Django stuff:
61 | *.log
62 | local_settings.py
63 | db.sqlite3
64 | db.sqlite3-journal
65 |
66 | # Flask stuff:
67 | instance/
68 | .webassets-cache
69 |
70 | # Scrapy stuff:
71 | .scrapy
72 |
73 | # Sphinx documentation
74 | docs/_build/
75 |
76 | # PyBuilder
77 | .pybuilder/
78 | target/
79 |
80 | # Jupyter Notebook
81 | .ipynb_checkpoints
82 |
83 | # IPython
84 | profile_default/
85 | ipython_config.py
86 |
87 | # pyenv
88 | # For a library or package, you might want to ignore these files since the code is
89 | # intended to run in multiple environments; otherwise, check them in:
90 | # .python-version
91 |
92 | # pipenv
93 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
94 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
95 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
96 | # install all needed dependencies.
97 | #Pipfile.lock
98 |
99 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
100 | __pypackages__/
101 |
102 | # Celery stuff
103 | celerybeat-schedule
104 | celerybeat.pid
105 |
106 | # SageMath parsed files
107 | *.sage.py
108 |
109 | # Environments
110 | .env
111 | .venv
112 | env/
113 | venv/
114 | ENV/
115 | env.bak/
116 | venv.bak/
117 |
118 | # Spyder project settings
119 | .spyderproject
120 | .spyproject
121 |
122 | # Rope project settings
123 | .ropeproject
124 |
125 | # mkdocs documentation
126 | /site
127 |
128 | # mypy
129 | .mypy_cache/
130 | .dmypy.json
131 | dmypy.json
132 |
133 | # Pyre type checker
134 | .pyre/
135 |
136 | # pytype static type analyzer
137 | .pytype/
138 |
139 | # Cython debug symbols
140 | cython_debug/
141 |
--------------------------------------------------------------------------------
/1. Thread Basics.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 1,
6 | "metadata": {},
7 | "outputs": [],
8 | "source": [
9 | "import time\n",
10 | "import random\n",
11 | "import threading\n",
12 | "from threading import Thread"
13 | ]
14 | },
15 | {
16 | "cell_type": "markdown",
17 | "metadata": {},
18 | "source": [
19 | "In Python 3, `threading` is the module used to create and use threads. There's a low level module `_thread` but it's not recommended to use it directly. I'm mentioning it just as a warning, **don't use `_thread`!**.\n",
20 | "\n",
21 | "The most important class in the `threading` module is: `Thread` (doh!).\n",
22 | "\n",
23 | "Very simplified, this is how a thread is instantiated:\n",
24 | "\n",
25 | "```python\n",
26 | "class Thread:\n",
27 | " def __init__(self, target, name=None, args=(), kwargs={}):\n",
28 | " pass\n",
29 | "```\n",
30 | "(there's a `group` argument which should be always `None`, as it's reserved for future use)\n",
31 | "\n",
32 | "In this case, `target` is the function that will be executed in that particular thread.\n",
33 | "\n",
34 | "Once a thread has been _created_ (instantiated), we'll need to `start()` it in order for it to begin to process."
35 | ]
36 | },
37 | {
38 | "cell_type": "markdown",
39 | "metadata": {},
40 | "source": [
41 | "#### Basic example of a thread"
42 | ]
43 | },
44 | {
45 | "cell_type": "code",
46 | "execution_count": 2,
47 | "metadata": {},
48 | "outputs": [],
49 | "source": [
50 | "def simple_worker():\n",
51 | " print('hello', flush=True)\n",
52 | " time.sleep(2)\n",
53 | " print('world', flush=True)"
54 | ]
55 | },
56 | {
57 | "cell_type": "code",
58 | "execution_count": 3,
59 | "metadata": {},
60 | "outputs": [],
61 | "source": [
62 | "t1 = Thread(target=simple_worker)"
63 | ]
64 | },
65 | {
66 | "cell_type": "code",
67 | "execution_count": 4,
68 | "metadata": {},
69 | "outputs": [
70 | {
71 | "name": "stdout",
72 | "output_type": "stream",
73 | "text": [
74 | "hello\n"
75 | ]
76 | }
77 | ],
78 | "source": [
79 | "t1.start()"
80 | ]
81 | },
82 | {
83 | "cell_type": "code",
84 | "execution_count": 5,
85 | "metadata": {},
86 | "outputs": [],
87 | "source": [
88 | "t1.join()"
89 | ]
90 | },
91 | {
92 | "cell_type": "markdown",
93 | "metadata": {},
94 | "source": [
95 | "#### Running multiple threads in parallel"
96 | ]
97 | },
98 | {
99 | "cell_type": "code",
100 | "execution_count": 10,
101 | "metadata": {},
102 | "outputs": [],
103 | "source": [
104 | "def simple_worker():\n",
105 | " time.sleep(random.random() * 5)\n",
106 | " value = random.randint(0, 99)\n",
107 | " print(f'My value: {value}')"
108 | ]
109 | },
110 | {
111 | "cell_type": "code",
112 | "execution_count": 11,
113 | "metadata": {},
114 | "outputs": [],
115 | "source": [
116 | "threads = [Thread(target=simple_worker) for _ in range(5)]"
117 | ]
118 | },
119 | {
120 | "cell_type": "code",
121 | "execution_count": 12,
122 | "metadata": {},
123 | "outputs": [
124 | {
125 | "data": {
126 | "text/plain": [
127 | "[None, None, None, None, None]"
128 | ]
129 | },
130 | "execution_count": 12,
131 | "metadata": {},
132 | "output_type": "execute_result"
133 | }
134 | ],
135 | "source": [
136 | "[t.start() for t in threads]"
137 | ]
138 | },
139 | {
140 | "cell_type": "code",
141 | "execution_count": 9,
142 | "metadata": {},
143 | "outputs": [
144 | {
145 | "name": "stdout",
146 | "output_type": "stream",
147 | "text": [
148 | "My value: 48\n",
149 | "My value: 94\n",
150 | "My value: 35\n",
151 | "My value: 16\n",
152 | "My value: 7\n"
153 | ]
154 | },
155 | {
156 | "data": {
157 | "text/plain": [
158 | "[None, None, None, None, None]"
159 | ]
160 | },
161 | "execution_count": 9,
162 | "metadata": {},
163 | "output_type": "execute_result"
164 | }
165 | ],
166 | "source": [
167 | "[t.join() for t in threads]"
168 | ]
169 | },
170 | {
171 | "cell_type": "markdown",
172 | "metadata": {},
173 | "source": [
174 | "#### Thread States\n",
175 | "\n",
176 | "A thread can be in multiple states, when a thread has just been created, its state is `\"ready\"`:"
177 | ]
178 | },
179 | {
180 | "cell_type": "code",
181 | "execution_count": null,
182 | "metadata": {},
183 | "outputs": [],
184 | "source": [
185 | "def simple_worker():\n",
186 | "\n",
187 | " print('Thread running...')\n",
188 | " time.sleep(5)\n",
189 | " print('Thread finished...')"
190 | ]
191 | },
192 | {
193 | "cell_type": "code",
194 | "execution_count": null,
195 | "metadata": {},
196 | "outputs": [],
197 | "source": [
198 | "t = Thread(target=simple_worker)"
199 | ]
200 | },
201 | {
202 | "cell_type": "code",
203 | "execution_count": null,
204 | "metadata": {},
205 | "outputs": [],
206 | "source": [
207 | "t.is_alive()"
208 | ]
209 | },
210 | {
211 | "cell_type": "code",
212 | "execution_count": null,
213 | "metadata": {},
214 | "outputs": [],
215 | "source": [
216 | "t.start()"
217 | ]
218 | },
219 | {
220 | "cell_type": "code",
221 | "execution_count": null,
222 | "metadata": {},
223 | "outputs": [],
224 | "source": [
225 | "t.is_alive()"
226 | ]
227 | },
228 | {
229 | "cell_type": "markdown",
230 | "metadata": {},
231 | "source": [
232 | "A thread that has finished can't be started again, as shown in the following example:"
233 | ]
234 | },
235 | {
236 | "cell_type": "code",
237 | "execution_count": null,
238 | "metadata": {},
239 | "outputs": [],
240 | "source": [
241 | "t.start()"
242 | ]
243 | },
244 | {
245 | "cell_type": "markdown",
246 | "metadata": {},
247 | "source": [
248 | "**Important:** It's not possible(\\*) to manage thread states manually, for example, stopping a thread. A thread always has to run its natural cycle.\n",
249 | "\n",
250 | "(\\*) You might find hacks in the internet on how to stop threads, but **it's a bad practice**. We'll discuss more later."
251 | ]
252 | },
253 | {
254 | "cell_type": "markdown",
255 | "metadata": {},
256 | "source": [
257 | "#### Thread Identity\n",
258 | "\n",
259 | "The thread class has two attributes that lets us identify each thread. The human-ready `name`, which we can set when we construct the thread, and the machine-oriented `ident` one"
260 | ]
261 | },
262 | {
263 | "cell_type": "code",
264 | "execution_count": null,
265 | "metadata": {},
266 | "outputs": [],
267 | "source": [
268 | "def simple_worker():\n",
269 | " print('Thread running...')\n",
270 | " time.sleep(5)\n",
271 | " print('Thread exiting...')"
272 | ]
273 | },
274 | {
275 | "cell_type": "code",
276 | "execution_count": null,
277 | "metadata": {},
278 | "outputs": [],
279 | "source": [
280 | "t = Thread(target=simple_worker)"
281 | ]
282 | },
283 | {
284 | "cell_type": "code",
285 | "execution_count": null,
286 | "metadata": {},
287 | "outputs": [],
288 | "source": [
289 | "t.name"
290 | ]
291 | },
292 | {
293 | "cell_type": "markdown",
294 | "metadata": {},
295 | "source": [
296 | "`ident` will be `None`until we run the thread."
297 | ]
298 | },
299 | {
300 | "cell_type": "code",
301 | "execution_count": null,
302 | "metadata": {},
303 | "outputs": [],
304 | "source": [
305 | "t.ident is None"
306 | ]
307 | },
308 | {
309 | "cell_type": "code",
310 | "execution_count": null,
311 | "metadata": {},
312 | "outputs": [],
313 | "source": [
314 | "t.start()"
315 | ]
316 | },
317 | {
318 | "cell_type": "code",
319 | "execution_count": null,
320 | "metadata": {},
321 | "outputs": [],
322 | "source": [
323 | "t.name"
324 | ]
325 | },
326 | {
327 | "cell_type": "code",
328 | "execution_count": null,
329 | "metadata": {},
330 | "outputs": [],
331 | "source": [
332 | "t.ident"
333 | ]
334 | },
335 | {
336 | "cell_type": "markdown",
337 | "metadata": {},
338 | "source": [
339 | "We can create a thread and assign a custom name to it:"
340 | ]
341 | },
342 | {
343 | "cell_type": "code",
344 | "execution_count": null,
345 | "metadata": {},
346 | "outputs": [],
347 | "source": [
348 | "t = Thread(target=simple_worker, name='PyCon 2020 Tutorial!')"
349 | ]
350 | },
351 | {
352 | "cell_type": "code",
353 | "execution_count": null,
354 | "metadata": {},
355 | "outputs": [],
356 | "source": [
357 | "t.start()"
358 | ]
359 | },
360 | {
361 | "cell_type": "code",
362 | "execution_count": null,
363 | "metadata": {},
364 | "outputs": [],
365 | "source": [
366 | "t.name"
367 | ]
368 | },
369 | {
370 | "cell_type": "code",
371 | "execution_count": null,
372 | "metadata": {},
373 | "outputs": [],
374 | "source": [
375 | "t.ident"
376 | ]
377 | },
378 | {
379 | "cell_type": "markdown",
380 | "metadata": {},
381 | "source": [
382 | "#### A thread knows itself\n",
383 | "\n",
384 | "It's also possible to know the identity of the thread from within the thread itself. It might be counter intuitive as we don't have the reference to the created object, but the module function `threading.currentThread()` will provide access to it."
385 | ]
386 | },
387 | {
388 | "cell_type": "code",
389 | "execution_count": null,
390 | "metadata": {},
391 | "outputs": [],
392 | "source": [
393 | "def simple_worker():\n",
394 | " sleep_secs = random.randint(1, 5)\n",
395 | " myself = threading.current_thread()\n",
396 | " ident = threading.get_ident()\n",
397 | " print(f\"I am thread {myself.name} (ID {ident}), and I'm sleeping for {sleep_secs}.\")\n",
398 | " time.sleep(sleep_secs)\n",
399 | " print(f'Thread {myself.name} exiting...')"
400 | ]
401 | },
402 | {
403 | "cell_type": "code",
404 | "execution_count": null,
405 | "metadata": {},
406 | "outputs": [],
407 | "source": [
408 | "t1 = Thread(target=simple_worker, name='Bubbles')\n",
409 | "t2 = Thread(target=simple_worker, name='Blossom')\n",
410 | "t3 = Thread(target=simple_worker, name='Buttercup')"
411 | ]
412 | },
413 | {
414 | "cell_type": "code",
415 | "execution_count": null,
416 | "metadata": {},
417 | "outputs": [],
418 | "source": [
419 | "t1.start()"
420 | ]
421 | },
422 | {
423 | "cell_type": "code",
424 | "execution_count": null,
425 | "metadata": {},
426 | "outputs": [],
427 | "source": [
428 | "t2.start()"
429 | ]
430 | },
431 | {
432 | "cell_type": "code",
433 | "execution_count": null,
434 | "metadata": {},
435 | "outputs": [],
436 | "source": [
437 | "t3.start()"
438 | ]
439 | },
440 | {
441 | "cell_type": "code",
442 | "execution_count": null,
443 | "metadata": {},
444 | "outputs": [],
445 | "source": [
446 | "print('Waiting...')"
447 | ]
448 | },
449 | {
450 | "cell_type": "markdown",
451 | "metadata": {},
452 | "source": [
453 | "#### Passing parameters to threads\n",
454 | "\n",
455 | "Passing parameters is simple with the thread constructor, just use the `args` argument:"
456 | ]
457 | },
458 | {
459 | "cell_type": "code",
460 | "execution_count": null,
461 | "metadata": {},
462 | "outputs": [],
463 | "source": [
464 | "def simple_worker(time_to_sleep):\n",
465 | " myself = threading.current_thread()\n",
466 | " ident = threading.get_ident()\n",
467 | " print(f\"I am thread {myself.name} (ID {ident}), and I'm sleeping for {time_to_sleep}.\")\n",
468 | " time.sleep(time_to_sleep)\n",
469 | " print(f'Thread {myself.name} exiting...')"
470 | ]
471 | },
472 | {
473 | "cell_type": "code",
474 | "execution_count": null,
475 | "metadata": {},
476 | "outputs": [],
477 | "source": [
478 | "t1 = Thread(target=simple_worker, name='Bubbles', args=(3, ))\n",
479 | "t2 = Thread(target=simple_worker, name='Blossom', args=(1.5, ))\n",
480 | "t3 = Thread(target=simple_worker, name='Buttercup', args=(2, ))"
481 | ]
482 | },
483 | {
484 | "cell_type": "code",
485 | "execution_count": null,
486 | "metadata": {},
487 | "outputs": [],
488 | "source": [
489 | "t1.start()"
490 | ]
491 | },
492 | {
493 | "cell_type": "code",
494 | "execution_count": null,
495 | "metadata": {},
496 | "outputs": [],
497 | "source": [
498 | "t2.start()"
499 | ]
500 | },
501 | {
502 | "cell_type": "code",
503 | "execution_count": null,
504 | "metadata": {},
505 | "outputs": [],
506 | "source": [
507 | "t3.start()"
508 | ]
509 | },
510 | {
511 | "cell_type": "markdown",
512 | "metadata": {},
513 | "source": [
514 | "#### Subclassing `Thread`\n",
515 | "\n",
516 | "So far, the way we've created threads is by passing a `target` function to be executed. There's an alternative, more OOP-way to do it, which is extending the Thread class:"
517 | ]
518 | },
519 | {
520 | "cell_type": "code",
521 | "execution_count": null,
522 | "metadata": {},
523 | "outputs": [],
524 | "source": [
525 | "class MyThread(Thread):\n",
526 | " def __init__(self, time_to_sleep, name=None):\n",
527 | " super().__init__(name=name)\n",
528 | " self.time_to_sleep = time_to_sleep\n",
529 | " \n",
530 | " def run(self):\n",
531 | " ident = threading.get_ident()\n",
532 | " print(f\"I am thread {self.name} (ID {ident}), and I'm sleeping for {self.time_to_sleep} secs.\")\n",
533 | " time.sleep(self.time_to_sleep)\n",
534 | " print(f'Thread {self.name} exiting...')"
535 | ]
536 | },
537 | {
538 | "cell_type": "code",
539 | "execution_count": null,
540 | "metadata": {},
541 | "outputs": [],
542 | "source": [
543 | "t = MyThread(2)"
544 | ]
545 | },
546 | {
547 | "cell_type": "code",
548 | "execution_count": null,
549 | "metadata": {},
550 | "outputs": [],
551 | "source": [
552 | "t.start()"
553 | ]
554 | },
555 | {
556 | "cell_type": "markdown",
557 | "metadata": {},
558 | "source": [
559 | "## Shared Data\n",
560 | "\n",
561 | "As we'll see, Threads can access shared data within the process they live in. Example:"
562 | ]
563 | },
564 | {
565 | "cell_type": "code",
566 | "execution_count": null,
567 | "metadata": {},
568 | "outputs": [],
569 | "source": [
570 | "TIME_TO_SLEEP = 2"
571 | ]
572 | },
573 | {
574 | "cell_type": "code",
575 | "execution_count": null,
576 | "metadata": {},
577 | "outputs": [],
578 | "source": [
579 | "def simple_worker():\n",
580 | " myself = threading.current_thread()\n",
581 | " print(f\"I am thread {myself.name}, and I'm sleeping for {TIME_TO_SLEEP}.\")\n",
582 | " time.sleep(TIME_TO_SLEEP)\n",
583 | " print(f'Thread {myself.name} exiting...')"
584 | ]
585 | },
586 | {
587 | "cell_type": "code",
588 | "execution_count": null,
589 | "metadata": {},
590 | "outputs": [],
591 | "source": [
592 | "t1 = Thread(target=simple_worker, name='Bubbles')\n",
593 | "t2 = Thread(target=simple_worker, name='Blossom')\n",
594 | "t3 = Thread(target=simple_worker, name='Buttercup')"
595 | ]
596 | },
597 | {
598 | "cell_type": "code",
599 | "execution_count": null,
600 | "metadata": {},
601 | "outputs": [],
602 | "source": [
603 | "t1.start()"
604 | ]
605 | },
606 | {
607 | "cell_type": "code",
608 | "execution_count": null,
609 | "metadata": {},
610 | "outputs": [],
611 | "source": [
612 | "t2.start()"
613 | ]
614 | },
615 | {
616 | "cell_type": "code",
617 | "execution_count": null,
618 | "metadata": {},
619 | "outputs": [],
620 | "source": [
621 | "t3.start()"
622 | ]
623 | },
624 | {
625 | "cell_type": "markdown",
626 | "metadata": {},
627 | "source": [
628 | "How is this possible?\n",
629 | "\n",
630 | "Remember, all threads live **within the same process**, and the variable `TIME_TO_SLEEP` is stored in the process. So all the threads created can access that variable."
631 | ]
632 | },
633 | {
634 | "cell_type": "markdown",
635 | "metadata": {},
636 | "source": [
637 | " "
638 | ]
639 | },
640 | {
641 | "cell_type": "markdown",
642 | "metadata": {},
643 | "source": [
644 | "## A real example\n",
645 | "\n",
646 | "In the `crypto-examples` directory, we've included a real example of a web server that contains prices of different cryptocurrencies. You can run it with `python flask_app.py --sleep [sleep in seconds]`. The server can be slowed down on purpose to simulate a real slow server.\n",
647 | "\n",
648 | "Let's check how to get one price as an example:"
649 | ]
650 | },
651 | {
652 | "cell_type": "code",
653 | "execution_count": null,
654 | "metadata": {},
655 | "outputs": [],
656 | "source": [
657 | "BASE_URL = \"http://localhost:5000\""
658 | ]
659 | },
660 | {
661 | "cell_type": "code",
662 | "execution_count": null,
663 | "metadata": {},
664 | "outputs": [],
665 | "source": [
666 | "import requests"
667 | ]
668 | },
669 | {
670 | "cell_type": "code",
671 | "execution_count": null,
672 | "metadata": {},
673 | "outputs": [],
674 | "source": [
675 | "resp = requests.get(f\"{BASE_URL}/price/bitfinex/btc/2020-04-01\")"
676 | ]
677 | },
678 | {
679 | "cell_type": "code",
680 | "execution_count": null,
681 | "metadata": {},
682 | "outputs": [],
683 | "source": [
684 | "resp"
685 | ]
686 | },
687 | {
688 | "cell_type": "code",
689 | "execution_count": null,
690 | "metadata": {},
691 | "outputs": [],
692 | "source": [
693 | "resp.json()"
694 | ]
695 | },
696 | {
697 | "cell_type": "markdown",
698 | "metadata": {},
699 | "source": [
700 | "Now, let's suppose we want to get the price of Bitcoin from 3 different exchanges: `bitfinex`, `bitstamp` and `kraken`. The sequential requests would take us 6 seconds (with a sleep param in 2)."
701 | ]
702 | },
703 | {
704 | "cell_type": "code",
705 | "execution_count": null,
706 | "metadata": {},
707 | "outputs": [],
708 | "source": [
709 | "EXCHANGES = ['bitfinex', 'bitstamp', 'kraken']"
710 | ]
711 | },
712 | {
713 | "cell_type": "code",
714 | "execution_count": null,
715 | "metadata": {},
716 | "outputs": [],
717 | "source": [
718 | "start = time.time()"
719 | ]
720 | },
721 | {
722 | "cell_type": "code",
723 | "execution_count": null,
724 | "metadata": {},
725 | "outputs": [],
726 | "source": [
727 | "for exchange in EXCHANGES:\n",
728 | " resp = requests.get(f\"{BASE_URL}/price/{exchange}/btc/2020-04-01\")\n",
729 | " print(f\"{exchange.title()}: ${resp.json()['close']}\")"
730 | ]
731 | },
732 | {
733 | "cell_type": "code",
734 | "execution_count": null,
735 | "metadata": {},
736 | "outputs": [],
737 | "source": [
738 | "time.time() - start"
739 | ]
740 | },
741 | {
742 | "cell_type": "markdown",
743 | "metadata": {},
744 | "source": [
745 | "Let's now move it to threads! For now, we'll just **print** the output, as we'll se data sharing in further lessons..."
746 | ]
747 | },
748 | {
749 | "cell_type": "code",
750 | "execution_count": null,
751 | "metadata": {},
752 | "outputs": [],
753 | "source": [
754 | "def check_price(exchange, symbol, date, base_url=BASE_URL):\n",
755 | " resp = requests.get(f\"{base_url}/price/{exchange}/{symbol}/{date}\")\n",
756 | " print(f\"{exchange.title()}: ${resp.json()['close']}\")"
757 | ]
758 | },
759 | {
760 | "cell_type": "code",
761 | "execution_count": null,
762 | "metadata": {},
763 | "outputs": [],
764 | "source": [
765 | "check_price('bitfinex', 'btc', '2020-04-01')"
766 | ]
767 | },
768 | {
769 | "cell_type": "code",
770 | "execution_count": null,
771 | "metadata": {},
772 | "outputs": [],
773 | "source": [
774 | "threads = [\n",
775 | " Thread(target=check_price, args=(exchange, 'btc', '2020-04-01'))\n",
776 | " for exchange in EXCHANGES\n",
777 | "]"
778 | ]
779 | },
780 | {
781 | "cell_type": "code",
782 | "execution_count": null,
783 | "metadata": {},
784 | "outputs": [],
785 | "source": [
786 | "start = time.time()"
787 | ]
788 | },
789 | {
790 | "cell_type": "code",
791 | "execution_count": null,
792 | "metadata": {},
793 | "outputs": [],
794 | "source": [
795 | "[t.start() for t in threads];"
796 | ]
797 | },
798 | {
799 | "cell_type": "code",
800 | "execution_count": null,
801 | "metadata": {},
802 | "outputs": [],
803 | "source": [
804 | "[t.join() for t in threads];"
805 | ]
806 | },
807 | {
808 | "cell_type": "code",
809 | "execution_count": null,
810 | "metadata": {},
811 | "outputs": [],
812 | "source": [
813 | "time.time() - start"
814 | ]
815 | },
816 | {
817 | "cell_type": "markdown",
818 | "metadata": {},
819 | "source": [
820 | "#### How many threads can we start?\n",
821 | "\n",
822 | "Let's say we need to get prices for 10 exchanges, 3 symbols, for a total of 30 days. Those are a lot of requests:"
823 | ]
824 | },
825 | {
826 | "cell_type": "code",
827 | "execution_count": null,
828 | "metadata": {},
829 | "outputs": [],
830 | "source": [
831 | "10 * 3 * 30"
832 | ]
833 | },
834 | {
835 | "cell_type": "markdown",
836 | "metadata": {},
837 | "source": [
838 | "Can we start 900 threads all at once? Sadly, we can't. Each threads consumes resources and too many threads are usually a problem."
839 | ]
840 | },
841 | {
842 | "cell_type": "markdown",
843 | "metadata": {},
844 | "source": [
845 | "So, what can we do when we need to process too many concurrent jobs? We'll create workers and use a consumer-producer model. But first, we need to talk about shared data, race conditions and synchronization..."
846 | ]
847 | },
848 | {
849 | "cell_type": "markdown",
850 | "metadata": {},
851 | "source": [
852 | "## Summary:\n",
853 | "\n",
854 | "* `threading` module ✅\n",
855 | "* `_thread` module ❌\n",
856 | "\n",
857 | "A thread's life cycle is Instantiated > Started > Running > Finished."
858 | ]
859 | }
860 | ],
861 | "metadata": {
862 | "kernelspec": {
863 | "display_name": "Python 3",
864 | "language": "python",
865 | "name": "python3"
866 | },
867 | "language_info": {
868 | "codemirror_mode": {
869 | "name": "ipython",
870 | "version": 3
871 | },
872 | "file_extension": ".py",
873 | "mimetype": "text/x-python",
874 | "name": "python",
875 | "nbconvert_exporter": "python",
876 | "pygments_lexer": "ipython3",
877 | "version": "3.8.0"
878 | }
879 | },
880 | "nbformat": 4,
881 | "nbformat_minor": 4
882 | }
883 |
--------------------------------------------------------------------------------
/3. Deadlocks.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 1,
6 | "metadata": {},
7 | "outputs": [],
8 | "source": [
9 | "import time\n",
10 | "import threading\n",
11 | "from threading import Thread\n",
12 | "import random\n",
13 | "import sys"
14 | ]
15 | },
16 | {
17 | "cell_type": "markdown",
18 | "metadata": {},
19 | "source": [
20 | "## Deadlocks\n",
21 | "\n",
22 | "Deadlocks are yet another problematic condition that might arise as the result of poorly synchronized code.\n",
23 | "\n",
24 | "#### A simple example\n",
25 | "\n",
26 | "Let's start by analyzing a simple example: movement between two \"bank accounts\":"
27 | ]
28 | },
29 | {
30 | "cell_type": "code",
31 | "execution_count": 2,
32 | "metadata": {},
33 | "outputs": [],
34 | "source": [
35 | "ACCOUNTS = {\n",
36 | " 'a1': 1000,\n",
37 | " 'a2': 1000\n",
38 | "}\n",
39 | "ITERATIONS = {\n",
40 | " 'a1': 0,\n",
41 | " 'a2': 0,\n",
42 | "}"
43 | ]
44 | },
45 | {
46 | "cell_type": "code",
47 | "execution_count": 3,
48 | "metadata": {},
49 | "outputs": [],
50 | "source": [
51 | "def move_funds(_from, _to, expected):\n",
52 | " name = threading.current_thread().name\n",
53 | "\n",
54 | " while True:\n",
55 | " amount = random.randint(0, 100)\n",
56 | " ACCOUNTS[_from] -= amount\n",
57 | " ACCOUNTS[_to] += amount\n",
58 | " total = sum(ACCOUNTS.values())\n",
59 | " if total != expected:\n",
60 | " print(f\"{name} found an inconsistent balance: ${total}\")\n",
61 | " break\n",
62 | " ITERATIONS[_from] += 1"
63 | ]
64 | },
65 | {
66 | "cell_type": "markdown",
67 | "metadata": {},
68 | "source": [
69 | "Your already trained eye will probably spot the issue with the previous function. The operations `ACCOUNTS[_from] -= amount` and `ACCOUNTS[_to] += amount` can potentially introduce race conditions. Let's verify it anyways:"
70 | ]
71 | },
72 | {
73 | "cell_type": "code",
74 | "execution_count": 4,
75 | "metadata": {},
76 | "outputs": [],
77 | "source": [
78 | "t1 = threading.Thread(target=move_funds, name='Thread-1', args=('a1', 'a2', 2000))\n",
79 | "t2 = threading.Thread(target=move_funds, name='Thread-2', args=('a2', 'a1', 2000))"
80 | ]
81 | },
82 | {
83 | "cell_type": "code",
84 | "execution_count": 5,
85 | "metadata": {},
86 | "outputs": [
87 | {
88 | "name": "stdout",
89 | "output_type": "stream",
90 | "text": [
91 | "Thread-2 found an inconsistent balance: $-244823Thread-1 found an inconsistent balance: $-244823\n",
92 | "\n"
93 | ]
94 | }
95 | ],
96 | "source": [
97 | "t1.start()\n",
98 | "t2.start()"
99 | ]
100 | },
101 | {
102 | "cell_type": "markdown",
103 | "metadata": {},
104 | "source": [
105 | "We've confirmed once again the potential dangers of multithreaded code."
106 | ]
107 | },
108 | {
109 | "cell_type": "markdown",
110 | "metadata": {},
111 | "source": [
112 | "#### Fixing it with Locks\n",
113 | "\n",
114 | "We've already learned about Locks, so we can use those to try synchronizing the access to the accounts. We'll create 2 locks, on for each account:"
115 | ]
116 | },
117 | {
118 | "cell_type": "code",
119 | "execution_count": 6,
120 | "metadata": {},
121 | "outputs": [],
122 | "source": [
123 | "ACCOUNTS = {\n",
124 | " 'a1': 1000,\n",
125 | " 'a2': 1000\n",
126 | "}"
127 | ]
128 | },
129 | {
130 | "cell_type": "code",
131 | "execution_count": 7,
132 | "metadata": {},
133 | "outputs": [],
134 | "source": [
135 | "lock_1 = threading.Lock()\n",
136 | "lock_2 = threading.Lock()"
137 | ]
138 | },
139 | {
140 | "cell_type": "code",
141 | "execution_count": 8,
142 | "metadata": {},
143 | "outputs": [],
144 | "source": [
145 | "def move_funds(_from, _to, lock_from, lock_to, expected):\n",
146 | " name = threading.current_thread().name\n",
147 | " iterations = 0\n",
148 | " while True:\n",
149 | " amount = random.randint(0, 100)\n",
150 | " with lock_from:\n",
151 | " with lock_to:\n",
152 | " ACCOUNTS[_from] -= amount\n",
153 | " ACCOUNTS[_to] += amount\n",
154 | " total = sum(ACCOUNTS.values())\n",
155 | " if total != expected:\n",
156 | " print(f\"{name} found an inconsistent balance: ${total}\")\n",
157 | " break\n",
158 | " iterations += 1\n",
159 | " if iterations > 1_000_000:\n",
160 | " print(f'{name} reached iteration limit. Stopping...')\n",
161 | " return"
162 | ]
163 | },
164 | {
165 | "cell_type": "code",
166 | "execution_count": 9,
167 | "metadata": {},
168 | "outputs": [],
169 | "source": [
170 | "t1 = threading.Thread(target=move_funds, name='Thread-1', args=('a1', 'a2', lock_1, lock_2, 2000))\n",
171 | "t2 = threading.Thread(target=move_funds, name='Thread-2', args=('a2', 'a1', lock_1, lock_2, 2000))"
172 | ]
173 | },
174 | {
175 | "cell_type": "code",
176 | "execution_count": 10,
177 | "metadata": {},
178 | "outputs": [],
179 | "source": [
180 | "t1.start()\n",
181 | "t2.start()"
182 | ]
183 | },
184 | {
185 | "cell_type": "code",
186 | "execution_count": 11,
187 | "metadata": {},
188 | "outputs": [
189 | {
190 | "name": "stdout",
191 | "output_type": "stream",
192 | "text": [
193 | "Thread-1 reached iteration limit. Stopping...\n",
194 | "Thread-2 reached iteration limit. Stopping...\n"
195 | ]
196 | }
197 | ],
198 | "source": [
199 | "[t.join() for t in (t1, t2)];"
200 | ]
201 | },
202 | {
203 | "cell_type": "code",
204 | "execution_count": 12,
205 | "metadata": {},
206 | "outputs": [
207 | {
208 | "data": {
209 | "text/plain": [
210 | "2000"
211 | ]
212 | },
213 | "execution_count": 12,
214 | "metadata": {},
215 | "output_type": "execute_result"
216 | }
217 | ],
218 | "source": [
219 | "sum(ACCOUNTS.values())"
220 | ]
221 | },
222 | {
223 | "cell_type": "code",
224 | "execution_count": 13,
225 | "metadata": {},
226 | "outputs": [
227 | {
228 | "data": {
229 | "text/plain": [
230 | "(False, False)"
231 | ]
232 | },
233 | "execution_count": 13,
234 | "metadata": {},
235 | "output_type": "execute_result"
236 | }
237 | ],
238 | "source": [
239 | "lock_1.locked(), lock_2.locked()"
240 | ]
241 | },
242 | {
243 | "cell_type": "markdown",
244 | "metadata": {},
245 | "source": [
246 | "**Worked!** Now the access to the accounts is protected by the locks. But there's a **very dangerous** potential situation hidden in our code. If we make just 1 tiny change, altering the order of the locks that are passed to our threads, we'll find ourselves deadlocked:"
247 | ]
248 | },
249 | {
250 | "cell_type": "code",
251 | "execution_count": 14,
252 | "metadata": {},
253 | "outputs": [],
254 | "source": [
255 | "ACCOUNTS = {\n",
256 | " 'a1': 1000,\n",
257 | " 'a2': 1000\n",
258 | "}"
259 | ]
260 | },
261 | {
262 | "cell_type": "code",
263 | "execution_count": 15,
264 | "metadata": {},
265 | "outputs": [],
266 | "source": [
267 | "t1 = threading.Thread(target=move_funds, name='Thread-1', args=('a1', 'a2', lock_1, lock_2, 2000))\n",
268 | "t2 = threading.Thread(target=move_funds, name='Thread-2', args=('a2', 'a1', lock_2, lock_1, 2000))"
269 | ]
270 | },
271 | {
272 | "cell_type": "code",
273 | "execution_count": 16,
274 | "metadata": {},
275 | "outputs": [],
276 | "source": [
277 | "t1.start()\n",
278 | "t2.start()"
279 | ]
280 | },
281 | {
282 | "cell_type": "markdown",
283 | "metadata": {},
284 | "source": [
285 | "We'll try to `join` the threads until they finish. This will probably hang forever:"
286 | ]
287 | },
288 | {
289 | "cell_type": "code",
290 | "execution_count": 17,
291 | "metadata": {},
292 | "outputs": [
293 | {
294 | "ename": "KeyboardInterrupt",
295 | "evalue": "",
296 | "output_type": "error",
297 | "traceback": [
298 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
299 | "\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)",
300 | "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;34m[\u001b[0m\u001b[0mt\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mt\u001b[0m \u001b[0;32min\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mt1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mt2\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m;\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
301 | "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m(.0)\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;34m[\u001b[0m\u001b[0mt\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mt\u001b[0m \u001b[0;32min\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mt1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mt2\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m;\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
302 | "\u001b[0;32m~/.pyenv/versions/3.8.0/lib/python3.8/threading.py\u001b[0m in \u001b[0;36mjoin\u001b[0;34m(self, timeout)\u001b[0m\n\u001b[1;32m 1009\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1010\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mtimeout\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1011\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_wait_for_tstate_lock\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1012\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1013\u001b[0m \u001b[0;31m# the behavior of a negative timeout isn't documented, but\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
303 | "\u001b[0;32m~/.pyenv/versions/3.8.0/lib/python3.8/threading.py\u001b[0m in \u001b[0;36m_wait_for_tstate_lock\u001b[0;34m(self, block, timeout)\u001b[0m\n\u001b[1;32m 1025\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mlock\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;31m# already determined that the C code is done\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1026\u001b[0m \u001b[0;32massert\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_is_stopped\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1027\u001b[0;31m \u001b[0;32melif\u001b[0m \u001b[0mlock\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0macquire\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mblock\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtimeout\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1028\u001b[0m \u001b[0mlock\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrelease\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1029\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_stop\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
304 | "\u001b[0;31mKeyboardInterrupt\u001b[0m: "
305 | ]
306 | }
307 | ],
308 | "source": [
309 | "[t.join() for t in (t1, t2)];"
310 | ]
311 | },
312 | {
313 | "cell_type": "markdown",
314 | "metadata": {},
315 | "source": [
316 | "And we'll see that NO thread has even started processing:"
317 | ]
318 | },
319 | {
320 | "cell_type": "code",
321 | "execution_count": 19,
322 | "metadata": {},
323 | "outputs": [
324 | {
325 | "data": {
326 | "text/plain": [
327 | "2000"
328 | ]
329 | },
330 | "execution_count": 19,
331 | "metadata": {},
332 | "output_type": "execute_result"
333 | }
334 | ],
335 | "source": [
336 | "sum(ACCOUNTS.values())"
337 | ]
338 | },
339 | {
340 | "cell_type": "markdown",
341 | "metadata": {},
342 | "source": [
343 | "Both locks are locked..."
344 | ]
345 | },
346 | {
347 | "cell_type": "code",
348 | "execution_count": 20,
349 | "metadata": {},
350 | "outputs": [
351 | {
352 | "data": {
353 | "text/plain": [
354 | "(True, True)"
355 | ]
356 | },
357 | "execution_count": 20,
358 | "metadata": {},
359 | "output_type": "execute_result"
360 | }
361 | ],
362 | "source": [
363 | "lock_1.locked(), lock_2.locked()"
364 | ]
365 | },
366 | {
367 | "cell_type": "markdown",
368 | "metadata": {},
369 | "source": [
370 | "Congratulations! You've just experienced your first: **DEADLOCK!**\n",
371 | "\n",
372 | "(we'll have to restart the Kernel here)."
373 | ]
374 | },
375 | {
376 | "cell_type": "markdown",
377 | "metadata": {},
378 | "source": [
379 | "#### What are Deadlocks and when do they happen?\n",
380 | "\n",
381 | "Wikipedia's [definition](https://en.wikipedia.org/wiki/Deadlock):\n",
382 | "\n",
383 | "> a deadlock is a state in which each member of a group is waiting for another member, including itself, to take action, such as sending a message or more commonly releasing a lock\n",
384 | "\n",
385 | "\n",
386 | "\n",
387 | "And this is one of my favorite quotes from an Operating Systems (_Avi Silberschatz_) book I read more than 10 years ago while I was still at school:\n",
388 | "\n",
389 | "> Perhaps the best illustration of a deadlock can be drawn from a law passed by the Kansas legislature early in the 20th century. It said, in part: “When two trains approach each other at a crossing, both shall come to a full stop and neither shall start up again until the other has gone.”\n",
390 | "\n",
391 | "\n",
392 | "In our code, the issue is the ordering in which the locks are acquired. This is a **VERY** difficult thing to spot.\n",
393 | "\n",
394 | "\n",
395 | "\n",
396 | "#### How to prevent Deadlocks\n",
397 | "\n",
398 | "Sorry, but I can't avoid telling you the truth: **It's very hard to prevent Deadlocks**. One simple technique is to always use timeouts when trying to acquire locks. If you're trying to acquire N shared locks, if you can't acquire all N, you can release them all and start over. Let's see an example:"
399 | ]
400 | },
401 | {
402 | "cell_type": "code",
403 | "execution_count": 45,
404 | "metadata": {},
405 | "outputs": [],
406 | "source": [
407 | "ACCOUNTS = {\n",
408 | " 'a1': 1000,\n",
409 | " 'a2': 1000\n",
410 | "}\n",
411 | "\n",
412 | "LOCK_TIMEOUT = .001\n",
413 | "\n",
414 | "lock_1 = threading.Lock()\n",
415 | "lock_2 = threading.Lock()"
416 | ]
417 | },
418 | {
419 | "cell_type": "code",
420 | "execution_count": 46,
421 | "metadata": {},
422 | "outputs": [],
423 | "source": [
424 | "def move_funds(_from, _to, lock_from, lock_to, expected):\n",
425 | " name = threading.current_thread().name\n",
426 | " iterations = 0\n",
427 | " while True:\n",
428 | " amount = random.randint(0, 100)\n",
429 | " locked = False\n",
430 | " while not locked:\n",
431 | " res1 = lock_from.acquire(timeout=LOCK_TIMEOUT)\n",
432 | " res2 = lock_to.acquire(timeout=LOCK_TIMEOUT)\n",
433 | " if all([res1, res2]):\n",
434 | " # Success! We acquired both locks\n",
435 | " locked = True\n",
436 | " else:\n",
437 | " # Release locks \"partially\" acquired\n",
438 | " if res1:\n",
439 | " lock_from.release()\n",
440 | " if res2:\n",
441 | " lock_to.release()\n",
442 | " # locked is True, we're safe\n",
443 | " ACCOUNTS[_from] -= amount\n",
444 | " ACCOUNTS[_to] += amount\n",
445 | " total = sum(ACCOUNTS.values())\n",
446 | " lock_from.release()\n",
447 | " lock_to.release()\n",
448 | " if total != expected:\n",
449 | " print(f\"{name} found an inconsistent balance: ${total}\")\n",
450 | " break\n",
451 | " iterations += 1\n",
452 | " if iterations > 100_000:\n",
453 | " print(f'{name} reached iteration limit. Stopping...')\n",
454 | " return"
455 | ]
456 | },
457 | {
458 | "cell_type": "code",
459 | "execution_count": 47,
460 | "metadata": {},
461 | "outputs": [],
462 | "source": [
463 | "t1 = threading.Thread(target=move_funds, name='Thread-1', args=('a1', 'a2', lock_1, lock_2, 2000))\n",
464 | "t2 = threading.Thread(target=move_funds, name='Thread-2', args=('a2', 'a1', lock_2, lock_1, 2000))"
465 | ]
466 | },
467 | {
468 | "cell_type": "code",
469 | "execution_count": 48,
470 | "metadata": {},
471 | "outputs": [],
472 | "source": [
473 | "t1.start()\n",
474 | "t2.start()"
475 | ]
476 | },
477 | {
478 | "cell_type": "code",
479 | "execution_count": 49,
480 | "metadata": {},
481 | "outputs": [
482 | {
483 | "name": "stdout",
484 | "output_type": "stream",
485 | "text": [
486 | "Thread-1 reached iteration limit. Stopping...\n",
487 | "Thread-2 reached iteration limit. Stopping...\n"
488 | ]
489 | }
490 | ],
491 | "source": [
492 | "[t.join() for t in (t1, t2)];"
493 | ]
494 | },
495 | {
496 | "cell_type": "code",
497 | "execution_count": 50,
498 | "metadata": {},
499 | "outputs": [
500 | {
501 | "data": {
502 | "text/plain": [
503 | "2000"
504 | ]
505 | },
506 | "execution_count": 50,
507 | "metadata": {},
508 | "output_type": "execute_result"
509 | }
510 | ],
511 | "source": [
512 | "sum(ACCOUNTS.values())"
513 | ]
514 | },
515 | {
516 | "cell_type": "code",
517 | "execution_count": 51,
518 | "metadata": {},
519 | "outputs": [
520 | {
521 | "data": {
522 | "text/plain": [
523 | "(False, False)"
524 | ]
525 | },
526 | "execution_count": 51,
527 | "metadata": {},
528 | "output_type": "execute_result"
529 | }
530 | ],
531 | "source": [
532 | "lock_1.locked(), lock_2.locked()"
533 | ]
534 | },
535 | {
536 | "cell_type": "markdown",
537 | "metadata": {},
538 | "source": [
539 | "As you can see, we've just prevented the deadlock. The key code piece that prevents the deadlock is the following:\n",
540 | "\n",
541 | "\n",
542 | "```python\n",
543 | "locked = False\n",
544 | "while not locked:\n",
545 | " res1 = lock_from.acquire(timeout=LOCK_TIMEOUT)\n",
546 | " res2 = lock_to.acquire(timeout=LOCK_TIMEOUT)\n",
547 | " if all([res1, res2]):\n",
548 | " # Success! We acquired both locks\n",
549 | " locked = True\n",
550 | " else:\n",
551 | " # Release locks \"partially\" acquired\n",
552 | " if res1:\n",
553 | " lock_from.release()\n",
554 | " if res2:\n",
555 | " lock_to.release() \n",
556 | "```\n",
557 | "\n",
558 | "If we successfully acquire both locks within the timeout window, we can proceed to perform our critical section. In other case, we'll release any \"partially\" acquired locks."
559 | ]
560 | },
561 | {
562 | "cell_type": "markdown",
563 | "metadata": {},
564 | "source": [
565 | "## Thread Synchronization: Summary\n",
566 | "\n",
567 | "There are several other synchronization mechanisms that we're not explicitly talking about, like `Semaphores`, `Conditions`, `Events`, `Barriers`, etc. These follow the same principles as locks, but are used for other purposes.\n",
568 | "\n",
569 | "The main takeaway from this lesson is: **synchronization is HARD, and error/bug prone**. Even the most experience developers avoid writing synchronized code, there's always something going wrong.\n",
570 | "\n",
571 | "Still, synchronization seems to be a necessary evil. We don't want to have race conditions in our code. In our following lessons we'll explore other alternatives to create correct code without the need of working with synchronization.\n",
572 | "\n",
573 | ""
574 | ]
575 | }
576 | ],
577 | "metadata": {
578 | "kernelspec": {
579 | "display_name": "Python 3",
580 | "language": "python",
581 | "name": "python3"
582 | },
583 | "language_info": {
584 | "codemirror_mode": {
585 | "name": "ipython",
586 | "version": 3
587 | },
588 | "file_extension": ".py",
589 | "mimetype": "text/x-python",
590 | "name": "python",
591 | "nbconvert_exporter": "python",
592 | "pygments_lexer": "ipython3",
593 | "version": "3.8.0"
594 | }
595 | },
596 | "nbformat": 4,
597 | "nbformat_minor": 4
598 | }
599 |
--------------------------------------------------------------------------------
/5. The Python GIL.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 95,
6 | "metadata": {},
7 | "outputs": [],
8 | "source": [
9 | "import time\n",
10 | "import itertools\n",
11 | "from queue import Queue\n",
12 | "from threading import Thread\n",
13 | "\n",
14 | "import requests"
15 | ]
16 | },
17 | {
18 | "cell_type": "code",
19 | "execution_count": 2,
20 | "metadata": {},
21 | "outputs": [],
22 | "source": [
23 | "def is_prime(n):\n",
24 | " if n in (2, 3):\n",
25 | " return True\n",
26 | " if n % 2 == 0:\n",
27 | " return False\n",
28 | " for divisor in range(3, n, 2):\n",
29 | " if n % divisor == 0:\n",
30 | " return False\n",
31 | " return True"
32 | ]
33 | },
34 | {
35 | "cell_type": "code",
36 | "execution_count": 3,
37 | "metadata": {},
38 | "outputs": [
39 | {
40 | "data": {
41 | "text/plain": [
42 | "True"
43 | ]
44 | },
45 | "execution_count": 3,
46 | "metadata": {},
47 | "output_type": "execute_result"
48 | }
49 | ],
50 | "source": [
51 | "is_prime(19)"
52 | ]
53 | },
54 | {
55 | "cell_type": "code",
56 | "execution_count": 6,
57 | "metadata": {},
58 | "outputs": [
59 | {
60 | "data": {
61 | "text/plain": [
62 | "False"
63 | ]
64 | },
65 | "execution_count": 6,
66 | "metadata": {},
67 | "output_type": "execute_result"
68 | }
69 | ],
70 | "source": [
71 | "is_prime(8)"
72 | ]
73 | },
74 | {
75 | "cell_type": "code",
76 | "execution_count": 13,
77 | "metadata": {},
78 | "outputs": [
79 | {
80 | "name": "stdout",
81 | "output_type": "stream",
82 | "text": [
83 | "15492781\n",
84 | "15492787\n",
85 | "15492803\n",
86 | "15492811\n",
87 | "15492810\n",
88 | "15492833\n",
89 | "15492859\n",
90 | "1549289\n"
91 | ]
92 | }
93 | ],
94 | "source": [
95 | "!head data/prime_mixture.txt"
96 | ]
97 | },
98 | {
99 | "cell_type": "code",
100 | "execution_count": 78,
101 | "metadata": {},
102 | "outputs": [],
103 | "source": [
104 | "with open('data/prime_mixture.txt') as fp:\n",
105 | " numbers = [int(n.strip()) for n in fp.read().split() if n]"
106 | ]
107 | },
108 | {
109 | "cell_type": "code",
110 | "execution_count": 79,
111 | "metadata": {},
112 | "outputs": [
113 | {
114 | "data": {
115 | "text/plain": [
116 | "[15492781, 15492787, 15492803, 15492811, 15492810]"
117 | ]
118 | },
119 | "execution_count": 79,
120 | "metadata": {},
121 | "output_type": "execute_result"
122 | }
123 | ],
124 | "source": [
125 | "numbers[:5]"
126 | ]
127 | },
128 | {
129 | "cell_type": "markdown",
130 | "metadata": {},
131 | "source": [
132 | "## Searching primes\n",
133 | "\n",
134 | "#### Understanding the problem, designing the solution\n",
135 | "\n",
136 | "As great software engineers we are, we'll start first by understanding our problem. How much time does it take to find out if a number is prime or not?"
137 | ]
138 | },
139 | {
140 | "cell_type": "code",
141 | "execution_count": 81,
142 | "metadata": {},
143 | "outputs": [],
144 | "source": [
145 | "start = time.time()"
146 | ]
147 | },
148 | {
149 | "cell_type": "code",
150 | "execution_count": 82,
151 | "metadata": {},
152 | "outputs": [
153 | {
154 | "data": {
155 | "text/plain": [
156 | "True"
157 | ]
158 | },
159 | "execution_count": 82,
160 | "metadata": {},
161 | "output_type": "execute_result"
162 | }
163 | ],
164 | "source": [
165 | "is_prime(numbers[0])"
166 | ]
167 | },
168 | {
169 | "cell_type": "code",
170 | "execution_count": 83,
171 | "metadata": {},
172 | "outputs": [
173 | {
174 | "data": {
175 | "text/plain": [
176 | "0.5961849689483643"
177 | ]
178 | },
179 | "execution_count": 83,
180 | "metadata": {},
181 | "output_type": "execute_result"
182 | }
183 | ],
184 | "source": [
185 | "time.time() - start"
186 | ]
187 | },
188 | {
189 | "cell_type": "markdown",
190 | "metadata": {},
191 | "source": [
192 | "Approximately 0.5 seconds (we don't need to be very accurate, don't worry). If we have 10 prime numbers:"
193 | ]
194 | },
195 | {
196 | "cell_type": "code",
197 | "execution_count": 80,
198 | "metadata": {},
199 | "outputs": [
200 | {
201 | "data": {
202 | "text/plain": [
203 | "10"
204 | ]
205 | },
206 | "execution_count": 80,
207 | "metadata": {},
208 | "output_type": "execute_result"
209 | }
210 | ],
211 | "source": [
212 | "len(numbers)"
213 | ]
214 | },
215 | {
216 | "cell_type": "markdown",
217 | "metadata": {},
218 | "source": [
219 | "we could expect a single threaded solution to take ~5 seconds. Let's start with that approach first:"
220 | ]
221 | },
222 | {
223 | "cell_type": "markdown",
224 | "metadata": {},
225 | "source": [
226 | "#### Single threaded approach\n",
227 | "\n",
228 | "A single threaded solution is the most basic one we can think of. No threads, locks, queues or concurrency. Plain old Python code to get the job done:"
229 | ]
230 | },
231 | {
232 | "cell_type": "code",
233 | "execution_count": 84,
234 | "metadata": {},
235 | "outputs": [],
236 | "source": [
237 | "def search_primes_single_thread(numbers):\n",
238 | " return [n for n in numbers if is_prime(n)]"
239 | ]
240 | },
241 | {
242 | "cell_type": "code",
243 | "execution_count": 85,
244 | "metadata": {},
245 | "outputs": [],
246 | "source": [
247 | "start = time.time()"
248 | ]
249 | },
250 | {
251 | "cell_type": "code",
252 | "execution_count": 86,
253 | "metadata": {},
254 | "outputs": [],
255 | "source": [
256 | "results = search_primes_single_thread(numbers)"
257 | ]
258 | },
259 | {
260 | "cell_type": "code",
261 | "execution_count": 87,
262 | "metadata": {},
263 | "outputs": [
264 | {
265 | "data": {
266 | "text/plain": [
267 | "'Time: 3.8426270484924316 seconds. Found 9 primes out of 10 total numbers.'"
268 | ]
269 | },
270 | "execution_count": 87,
271 | "metadata": {},
272 | "output_type": "execute_result"
273 | }
274 | ],
275 | "source": [
276 | "f\"Time: {time.time() - start} seconds. Found {len(results)} primes out of {len(numbers)} total numbers.\""
277 | ]
278 | },
279 | {
280 | "cell_type": "markdown",
281 | "metadata": {},
282 | "source": [
283 | "As we can see, it took less than 5 seconds, but it's within the same order of magnitude: between 1 and 10 seconds."
284 | ]
285 | },
286 | {
287 | "cell_type": "markdown",
288 | "metadata": {},
289 | "source": [
290 | "#### Speeding things up with multiple threads\n",
291 | "\n",
292 | "We quickly realize that we could improve a lot the solution by using multiple threads. If I have 16 cores in this computer, each one of them can calculate a prime at the same time, and we'll be done a lot quicker. How quicker? Well, assuming I have 16 cores, and each core will definitively take 1 number to process, our solution should take no more than a second. The slowest prime to compute will be the total time.\n",
293 | "\n",
294 | "Let's try it out!"
295 | ]
296 | },
297 | {
298 | "cell_type": "code",
299 | "execution_count": 88,
300 | "metadata": {},
301 | "outputs": [],
302 | "source": [
303 | "def check_prime_worker(number, results):\n",
304 | " if is_prime(number):\n",
305 | " results.append(number)"
306 | ]
307 | },
308 | {
309 | "cell_type": "markdown",
310 | "metadata": {},
311 | "source": [
312 | "(We should potentially use a thread safe collection, I know in CPython list append operations are thread safe)"
313 | ]
314 | },
315 | {
316 | "cell_type": "code",
317 | "execution_count": 89,
318 | "metadata": {},
319 | "outputs": [],
320 | "source": [
321 | "results = []"
322 | ]
323 | },
324 | {
325 | "cell_type": "code",
326 | "execution_count": 90,
327 | "metadata": {},
328 | "outputs": [],
329 | "source": [
330 | "threads = [Thread(target=check_prime_worker, args=(number, results)) for number in numbers]"
331 | ]
332 | },
333 | {
334 | "cell_type": "code",
335 | "execution_count": 91,
336 | "metadata": {},
337 | "outputs": [],
338 | "source": [
339 | "start = time.time()"
340 | ]
341 | },
342 | {
343 | "cell_type": "code",
344 | "execution_count": 92,
345 | "metadata": {},
346 | "outputs": [],
347 | "source": [
348 | "[t.start() for t in threads];"
349 | ]
350 | },
351 | {
352 | "cell_type": "code",
353 | "execution_count": 93,
354 | "metadata": {},
355 | "outputs": [],
356 | "source": [
357 | "[t.join() for t in threads];"
358 | ]
359 | },
360 | {
361 | "cell_type": "code",
362 | "execution_count": 94,
363 | "metadata": {},
364 | "outputs": [
365 | {
366 | "data": {
367 | "text/plain": [
368 | "'Time: 4.015429973602295 seconds. Found 9 primes out of 10 total numbers.'"
369 | ]
370 | },
371 | "execution_count": 94,
372 | "metadata": {},
373 | "output_type": "execute_result"
374 | }
375 | ],
376 | "source": [
377 | "f\"Time: {time.time() - start} seconds. Found {len(results)} primes out of {len(numbers)} total numbers.\""
378 | ]
379 | },
380 | {
381 | "cell_type": "markdown",
382 | "metadata": {},
383 | "source": [
384 | "**WHAT! 😦** 4 seconds! That's even slower than the sequential single-threaded solution. What is going on here? 🤔\n",
385 | "\n",
386 | "Congratulations, let me introduce you to:\n",
387 | "\n",
388 | "\n",
389 | " \n",
390 | " "
391 | ]
392 | },
393 | {
394 | "cell_type": "markdown",
395 | "metadata": {},
396 | "source": [
397 | "## What is the GIL?\n",
398 | "\n",
399 | "The GIL is a safety mechanism added to **cpython** (and Cpython only) that prevents multiple threads from running at the same time. This is completely counter intuitive, as the objective of having multiple threads is to make them run **in parallel**: that is, 2 or more at the same time.\n",
400 | "\n",
401 | "Understanding the GIL is outside the scope of this presentation. There's one important point that you should understand about the GIL: your threads will \"suffer\" the GIL **ONLY** if you're running CPU Bound tasks. I/O tasks will _release_ the GIL and let other threads run.\n",
402 | "\n",
403 | "\n",
404 | "If you want to know more about the GIL, this talk from Larry Hastings is just amazing: https://www.youtube.com/watch?v=KVKufdTphKs\n",
405 | "As a summary:\n",
406 | "\n",
407 | "* The GIL is a necessary evil. It made Python's C API simple to understand and extend.\n",
408 | "* Guido is open to remove the GIL, ONLY if it doesn't hurt performance (Larry demonstrated in another talk that it's very hard to achieve that)\n",
409 | "* Only CPython has a GIL. Other interpreters (Jython, PyPy, Ironpython) don't.\n",
410 | "* The GIL was \"broken\" in Python 2, but has been fixed in Python 3.2. We've reached \"peak\" performance so Larry doesn't think there will be significant improvements there."
411 | ]
412 | },
413 | {
414 | "cell_type": "markdown",
415 | "metadata": {},
416 | "source": [
417 | "## An I/O bound demonstration\n",
418 | "\n",
419 | "Let's revisit the example from our first lesson and run an I/O bound task multithreaded to prove the GIL is not an issue in this case.\n",
420 | "\n",
421 | "#### Sequential first\n",
422 | "\n",
423 | "We'll check our crypto prices again. This time, each request is artificially delayed 3 seconds. We'll check only 3 prices, for the same date, so it's going to take approximately 6 seconds:"
424 | ]
425 | },
426 | {
427 | "cell_type": "code",
428 | "execution_count": 96,
429 | "metadata": {},
430 | "outputs": [],
431 | "source": [
432 | "BASE_URL = \"http://localhost:5000\""
433 | ]
434 | },
435 | {
436 | "cell_type": "code",
437 | "execution_count": 97,
438 | "metadata": {},
439 | "outputs": [],
440 | "source": [
441 | "EXCHANGES = ['bitfinex', 'bitstamp', 'kraken']"
442 | ]
443 | },
444 | {
445 | "cell_type": "code",
446 | "execution_count": 98,
447 | "metadata": {},
448 | "outputs": [],
449 | "source": [
450 | "start = time.time()"
451 | ]
452 | },
453 | {
454 | "cell_type": "code",
455 | "execution_count": 99,
456 | "metadata": {},
457 | "outputs": [],
458 | "source": [
459 | "prices = [\n",
460 | " requests.get(f\"{BASE_URL}/price/{exchange}/btc/2020-04-01\").json()['close']\n",
461 | " for exchange in EXCHANGES\n",
462 | "]"
463 | ]
464 | },
465 | {
466 | "cell_type": "code",
467 | "execution_count": 100,
468 | "metadata": {},
469 | "outputs": [
470 | {
471 | "data": {
472 | "text/plain": [
473 | "6.828252077102661"
474 | ]
475 | },
476 | "execution_count": 100,
477 | "metadata": {},
478 | "output_type": "execute_result"
479 | }
480 | ],
481 | "source": [
482 | "time.time() - start"
483 | ]
484 | },
485 | {
486 | "cell_type": "markdown",
487 | "metadata": {},
488 | "source": [
489 | "As expected, ~6 seconds.\n",
490 | "\n",
491 | "#### Now, the multithreaded version\n",
492 | "\n",
493 | "We'll try now the same example with multiple threads. If our claims about the GIL being released by I/O Bound tasks is true, then the whole task should take around 2 seconds, let's check it out:"
494 | ]
495 | },
496 | {
497 | "cell_type": "code",
498 | "execution_count": 101,
499 | "metadata": {},
500 | "outputs": [],
501 | "source": [
502 | "def check_price(exchange, results):\n",
503 | " BASE_URL = \"http://localhost:5000\"\n",
504 | " return requests.get(f\"{BASE_URL}/price/{exchange}/btc/2020-04-01\").json()['close']"
505 | ]
506 | },
507 | {
508 | "cell_type": "code",
509 | "execution_count": 108,
510 | "metadata": {},
511 | "outputs": [],
512 | "source": [
513 | "results = []"
514 | ]
515 | },
516 | {
517 | "cell_type": "code",
518 | "execution_count": 109,
519 | "metadata": {},
520 | "outputs": [],
521 | "source": [
522 | "threads = [Thread(target=check_price, args=(exchange, results)) for exchange in EXCHANGES]"
523 | ]
524 | },
525 | {
526 | "cell_type": "code",
527 | "execution_count": 110,
528 | "metadata": {},
529 | "outputs": [],
530 | "source": [
531 | "start = time.time()"
532 | ]
533 | },
534 | {
535 | "cell_type": "code",
536 | "execution_count": 111,
537 | "metadata": {},
538 | "outputs": [],
539 | "source": [
540 | "[t.start() for t in threads];"
541 | ]
542 | },
543 | {
544 | "cell_type": "code",
545 | "execution_count": 112,
546 | "metadata": {},
547 | "outputs": [],
548 | "source": [
549 | "[t.join() for t in threads];"
550 | ]
551 | },
552 | {
553 | "cell_type": "code",
554 | "execution_count": 113,
555 | "metadata": {},
556 | "outputs": [
557 | {
558 | "data": {
559 | "text/plain": [
560 | "2.3815770149230957"
561 | ]
562 | },
563 | "execution_count": 113,
564 | "metadata": {},
565 | "output_type": "execute_result"
566 | }
567 | ],
568 | "source": [
569 | "time.time() - start"
570 | ]
571 | },
572 | {
573 | "cell_type": "markdown",
574 | "metadata": {},
575 | "source": [
576 | "Success! We've now corroborated that the GIL is actually released when waiting for I/O, which makes our programs \"feel\" like running in parallel.\n",
577 | "\n",
578 | "## Summary\n",
579 | "\n",
580 | "In this lesson we learned about one of the most \"hated\" features of Python: the GIL. Every post you read that is titled \"why not Python?\" or \"Python vs \\[insert language\\]\" will mention the GIL as a major drawback.\n",
581 | "\n",
582 | "In our next lesson we'll learn how we can improve our code if it's CPU bound."
583 | ]
584 | }
585 | ],
586 | "metadata": {
587 | "kernelspec": {
588 | "display_name": "Python 3",
589 | "language": "python",
590 | "name": "python3"
591 | },
592 | "language_info": {
593 | "codemirror_mode": {
594 | "name": "ipython",
595 | "version": 3
596 | },
597 | "file_extension": ".py",
598 | "mimetype": "text/x-python",
599 | "name": "python",
600 | "nbconvert_exporter": "python",
601 | "pygments_lexer": "ipython3",
602 | "version": "3.8.0"
603 | }
604 | },
605 | "nbformat": 4,
606 | "nbformat_minor": 4
607 | }
608 |
--------------------------------------------------------------------------------
/6. Multiprocessing.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 1,
6 | "metadata": {},
7 | "outputs": [],
8 | "source": [
9 | "import time\n",
10 | "import queue\n",
11 | "import random\n",
12 | "import multiprocessing as mp\n",
13 | "from multiprocessing import Process"
14 | ]
15 | },
16 | {
17 | "cell_type": "code",
18 | "execution_count": 2,
19 | "metadata": {},
20 | "outputs": [],
21 | "source": [
22 | "# Don't worry about this for now...\n",
23 | "mp.set_start_method('fork')"
24 | ]
25 | },
26 | {
27 | "cell_type": "markdown",
28 | "metadata": {},
29 | "source": [
30 | "## Multiprocessing\n",
31 | "\n",
32 | "The second most common way to write concurrent programs is with the use of multiprocessing (I'm leaving asyncio aside). It's the oldest concurrency concept, as processes predate threads.\n",
33 | "\n",
34 | "In a multiprocessing program, we'll create multiple processes that will be ran concurrently (and potentially in parallel) **by the operating system**. It's important to stress the fact that when we write multiprocessing code, we're giving full authority to the OS to manage and schedule our processes.\n",
35 | "\n",
36 | "#### How can multiprocessing help with the GIL?\n",
37 | "\n",
38 | "The main issue with the GIL was to protect **shared data** (low level data like reference counts) between threads. But, what if there's NO shared data at all? If you remember from our OS review before, data is shared only **within the same process**. Different processes DON'T share data. Which means that there's no GIL to worry about.\n",
39 | "\n",
40 | "#### Then, why not to use multiprocessing all the time?\n",
41 | "\n",
42 | "If multiprocessing doesn't suffer from the GIL, why not to use it instead of multithreading? As usual with computers, there's no free lunch. Multiprocessing suffers from 2 major drawbacks:\n",
43 | "\n",
44 | "##### 1. Slower, resource heavy\n",
45 | "\n",
46 | "Creating new processes is a lot slower than spawning threads. And by spawning new processes, we're duplicating all the information of our processes: share data, file descriptors, etc.\n",
47 | "\n",
48 | "\n",
49 | " \n",
50 | " \n",
51 | "\n",
52 | "\n",
53 | "##### 2. Hard to orchestrate\n",
54 | "\n",
55 | "As processes don't share data, it's hard to coordinate results and flags between multiple processes. We'll see this in an example later."
56 | ]
57 | },
58 | {
59 | "cell_type": "markdown",
60 | "metadata": {},
61 | "source": [
62 | "## The `Process` API\n",
63 | "\n",
64 | "The `multiprocessing` module has a `Process` class with an API very similar to the one in `threading.Thread`.\n",
65 | "\n",
66 | "> **Warning**: Always make sure you're using the module `multiprocessing` and not `subprocess`.\n",
67 | "\n",
68 | "Let's see an example:"
69 | ]
70 | },
71 | {
72 | "cell_type": "code",
73 | "execution_count": 3,
74 | "metadata": {},
75 | "outputs": [],
76 | "source": [
77 | "def say_hello():\n",
78 | " myself = mp.current_process()\n",
79 | " print(f'Hello World from \"{myself.name}\"')"
80 | ]
81 | },
82 | {
83 | "cell_type": "code",
84 | "execution_count": 4,
85 | "metadata": {},
86 | "outputs": [],
87 | "source": [
88 | "p = Process(target=say_hello, name=\"My First Process\")"
89 | ]
90 | },
91 | {
92 | "cell_type": "code",
93 | "execution_count": 5,
94 | "metadata": {},
95 | "outputs": [
96 | {
97 | "name": "stdout",
98 | "output_type": "stream",
99 | "text": [
100 | "Hello World from \"My First Process\"\n"
101 | ]
102 | }
103 | ],
104 | "source": [
105 | "p.start()"
106 | ]
107 | },
108 | {
109 | "cell_type": "code",
110 | "execution_count": 6,
111 | "metadata": {},
112 | "outputs": [],
113 | "source": [
114 | "p.join()"
115 | ]
116 | },
117 | {
118 | "cell_type": "markdown",
119 | "metadata": {},
120 | "source": [
121 | "It's important to free up resources allocated by such process:"
122 | ]
123 | },
124 | {
125 | "cell_type": "code",
126 | "execution_count": 7,
127 | "metadata": {},
128 | "outputs": [],
129 | "source": [
130 | "p.close()"
131 | ]
132 | },
133 | {
134 | "cell_type": "markdown",
135 | "metadata": {},
136 | "source": [
137 | "## Find prime example\n",
138 | "\n",
139 | "Let's verify if processes can actually run in parallel by running again our \"check primes\" example. If that's the case, we'll see a significant improvement in time. Remember, our multi-threaded version took ~4 seconds to process all 10 numbers."
140 | ]
141 | },
142 | {
143 | "cell_type": "code",
144 | "execution_count": 8,
145 | "metadata": {},
146 | "outputs": [],
147 | "source": [
148 | "def is_prime(n):\n",
149 | " if n in (2, 3):\n",
150 | " return True\n",
151 | " if n % 2 == 0:\n",
152 | " return False\n",
153 | " for divisor in range(3, n, 2):\n",
154 | " if n % divisor == 0:\n",
155 | " return False\n",
156 | " return True"
157 | ]
158 | },
159 | {
160 | "cell_type": "code",
161 | "execution_count": 9,
162 | "metadata": {},
163 | "outputs": [],
164 | "source": [
165 | "with open('data/prime_mixture.txt') as fp:\n",
166 | " numbers = [int(n.strip()) for n in fp.read().split() if n]"
167 | ]
168 | },
169 | {
170 | "cell_type": "code",
171 | "execution_count": 10,
172 | "metadata": {},
173 | "outputs": [
174 | {
175 | "data": {
176 | "text/plain": [
177 | "[15492781, 15492787, 15492803, 15492811, 15492810]"
178 | ]
179 | },
180 | "execution_count": 10,
181 | "metadata": {},
182 | "output_type": "execute_result"
183 | }
184 | ],
185 | "source": [
186 | "numbers[:5]"
187 | ]
188 | },
189 | {
190 | "cell_type": "code",
191 | "execution_count": 11,
192 | "metadata": {},
193 | "outputs": [],
194 | "source": [
195 | "def check_prime_worker(number):\n",
196 | " if is_prime(number):\n",
197 | " print(f'{number} IS PRIME ✅', flush=True)\n",
198 | " else:\n",
199 | " print(f'{number} IS NOT PRIME ❌', flush=True)"
200 | ]
201 | },
202 | {
203 | "cell_type": "code",
204 | "execution_count": 18,
205 | "metadata": {},
206 | "outputs": [],
207 | "source": [
208 | "processes = [Process(target=check_prime_worker, args=(number,)) for number in numbers]"
209 | ]
210 | },
211 | {
212 | "cell_type": "code",
213 | "execution_count": 19,
214 | "metadata": {},
215 | "outputs": [],
216 | "source": [
217 | "start = time.time()"
218 | ]
219 | },
220 | {
221 | "cell_type": "code",
222 | "execution_count": 20,
223 | "metadata": {},
224 | "outputs": [
225 | {
226 | "name": "stdout",
227 | "output_type": "stream",
228 | "text": [
229 | "15492810 IS NOT PRIME ❌\n",
230 | "15492811 IS PRIME ✅\n",
231 | "15492803 IS PRIME ✅\n",
232 | "15492859 IS PRIME ✅\n",
233 | "15527509 IS PRIME ✅\n",
234 | "15492781 IS PRIME ✅\n",
235 | "15492833 IS PRIME ✅\n",
236 | "15492787 IS PRIME ✅\n",
237 | "15502547 IS PRIME ✅\n",
238 | "15520301 IS PRIME ✅\n"
239 | ]
240 | }
241 | ],
242 | "source": [
243 | "[p.start() for p in processes];"
244 | ]
245 | },
246 | {
247 | "cell_type": "code",
248 | "execution_count": 21,
249 | "metadata": {},
250 | "outputs": [],
251 | "source": [
252 | "[p.join() for p in processes];"
253 | ]
254 | },
255 | {
256 | "cell_type": "code",
257 | "execution_count": 22,
258 | "metadata": {},
259 | "outputs": [
260 | {
261 | "data": {
262 | "text/plain": [
263 | "0.7560350894927979"
264 | ]
265 | },
266 | "execution_count": 22,
267 | "metadata": {},
268 | "output_type": "execute_result"
269 | }
270 | ],
271 | "source": [
272 | "time.time() - start"
273 | ]
274 | },
275 | {
276 | "cell_type": "code",
277 | "execution_count": 23,
278 | "metadata": {},
279 | "outputs": [],
280 | "source": [
281 | "[p.close() for p in processes];"
282 | ]
283 | },
284 | {
285 | "cell_type": "markdown",
286 | "metadata": {},
287 | "source": [
288 | "We can see a clear running time improvement, from ~4 seconds to ~0.7, which means that processes are indeed running in parallel."
289 | ]
290 | },
291 | {
292 | "cell_type": "markdown",
293 | "metadata": {},
294 | "source": [
295 | "## Sharing data with processes\n",
296 | "\n",
297 | "It's not as simple as with threads to share data. In our multithreaded example, we just passed a `results` dictionary that was used by the threads to store their results. In our case, we can't do that and we just had to print the result, which is useless for a real life program.\n",
298 | "\n",
299 | "There are several mechanisms to share data with multiprocessing, in this lesson we'll focus in `Queue`s and `Pipe`s."
300 | ]
301 | },
302 | {
303 | "cell_type": "markdown",
304 | "metadata": {},
305 | "source": [
306 | "#### Queues\n",
307 | "\n",
308 | "Queues in the multiprocessing module have a very similar API than the thread safe ones in the `queue` module, so let's just see an example:"
309 | ]
310 | },
311 | {
312 | "cell_type": "code",
313 | "execution_count": 24,
314 | "metadata": {},
315 | "outputs": [],
316 | "source": [
317 | "work_to_do = mp.JoinableQueue()\n",
318 | "work_done = mp.SimpleQueue()"
319 | ]
320 | },
321 | {
322 | "cell_type": "code",
323 | "execution_count": 25,
324 | "metadata": {},
325 | "outputs": [],
326 | "source": [
327 | "[work_to_do.put(n) for n in numbers];"
328 | ]
329 | },
330 | {
331 | "cell_type": "code",
332 | "execution_count": 26,
333 | "metadata": {},
334 | "outputs": [],
335 | "source": [
336 | "MAX_WORKERS = 5"
337 | ]
338 | },
339 | {
340 | "cell_type": "code",
341 | "execution_count": 27,
342 | "metadata": {},
343 | "outputs": [],
344 | "source": [
345 | "def process_consumer(task_queue, results_queue):\n",
346 | " while True:\n",
347 | " try:\n",
348 | " number = task_queue.get_nowait()\n",
349 | " result = is_prime(number)\n",
350 | " results_queue.put((number, result))\n",
351 | " except queue.Empty:\n",
352 | " print('No more numbers to process. Exiting...')\n",
353 | " return"
354 | ]
355 | },
356 | {
357 | "cell_type": "code",
358 | "execution_count": 28,
359 | "metadata": {},
360 | "outputs": [],
361 | "source": [
362 | "process_pool = [Process(target=process_consumer, args=(work_to_do, work_done)) for _ in range(MAX_WORKERS)]"
363 | ]
364 | },
365 | {
366 | "cell_type": "code",
367 | "execution_count": 29,
368 | "metadata": {},
369 | "outputs": [
370 | {
371 | "name": "stdout",
372 | "output_type": "stream",
373 | "text": [
374 | "No more numbers to process. Exiting...\n",
375 | "No more numbers to process. Exiting...\n",
376 | "No more numbers to process. Exiting...\n",
377 | "No more numbers to process. Exiting...\n",
378 | "No more numbers to process. Exiting...\n"
379 | ]
380 | }
381 | ],
382 | "source": [
383 | "[p.start() for p in process_pool];"
384 | ]
385 | },
386 | {
387 | "cell_type": "code",
388 | "execution_count": 30,
389 | "metadata": {},
390 | "outputs": [],
391 | "source": [
392 | "[p.join() for p in process_pool];"
393 | ]
394 | },
395 | {
396 | "cell_type": "code",
397 | "execution_count": 31,
398 | "metadata": {},
399 | "outputs": [],
400 | "source": [
401 | "[p.close() for p in process_pool];"
402 | ]
403 | },
404 | {
405 | "cell_type": "code",
406 | "execution_count": 32,
407 | "metadata": {},
408 | "outputs": [
409 | {
410 | "name": "stdout",
411 | "output_type": "stream",
412 | "text": [
413 | "15492810 IS NOT PRIME ❌\n",
414 | "15492781 IS PRIME ✅\n",
415 | "15492787 IS PRIME ✅\n",
416 | "15492803 IS PRIME ✅\n",
417 | "15492833 IS PRIME ✅\n",
418 | "15492811 IS PRIME ✅\n",
419 | "15492859 IS PRIME ✅\n",
420 | "15502547 IS PRIME ✅\n",
421 | "15520301 IS PRIME ✅\n",
422 | "15527509 IS PRIME ✅\n"
423 | ]
424 | }
425 | ],
426 | "source": [
427 | "while not work_done.empty():\n",
428 | " number, prime = work_done.get()\n",
429 | " if prime:\n",
430 | " print(f'{number} IS PRIME ✅')\n",
431 | " else:\n",
432 | " print(f'{number} IS NOT PRIME ❌')"
433 | ]
434 | },
435 | {
436 | "cell_type": "markdown",
437 | "metadata": {},
438 | "source": [
439 | "#### Pipes\n",
440 | "\n",
441 | "Pipes are not as safe as Queues, as data can be corrupted and it's hard to know when to start polling the queue. In our following example, we're assuming we're going to receive all 10 messages that we're expecting to receive given we're starting 10 processes. But the reality is that one of those processes could die before sending the message and we're going to wait forever."
442 | ]
443 | },
444 | {
445 | "cell_type": "code",
446 | "execution_count": 33,
447 | "metadata": {},
448 | "outputs": [],
449 | "source": [
450 | "main_conn, worker_conn = mp.Pipe()"
451 | ]
452 | },
453 | {
454 | "cell_type": "code",
455 | "execution_count": 34,
456 | "metadata": {},
457 | "outputs": [],
458 | "source": [
459 | "def process_prime_worker(number, pipe_connection):\n",
460 | " result = is_prime(number)\n",
461 | " pipe_connection.send((number, result))"
462 | ]
463 | },
464 | {
465 | "cell_type": "code",
466 | "execution_count": 35,
467 | "metadata": {},
468 | "outputs": [],
469 | "source": [
470 | "processes = [Process(target=process_prime_worker, args=(number, worker_conn)) for number in numbers]"
471 | ]
472 | },
473 | {
474 | "cell_type": "code",
475 | "execution_count": 36,
476 | "metadata": {},
477 | "outputs": [],
478 | "source": [
479 | "[p.start() for p in processes];"
480 | ]
481 | },
482 | {
483 | "cell_type": "code",
484 | "execution_count": 37,
485 | "metadata": {},
486 | "outputs": [],
487 | "source": [
488 | "[p.join() for p in processes];"
489 | ]
490 | },
491 | {
492 | "cell_type": "code",
493 | "execution_count": 38,
494 | "metadata": {},
495 | "outputs": [],
496 | "source": [
497 | "[p.close() for p in processes];"
498 | ]
499 | },
500 | {
501 | "cell_type": "code",
502 | "execution_count": 39,
503 | "metadata": {},
504 | "outputs": [
505 | {
506 | "name": "stdout",
507 | "output_type": "stream",
508 | "text": [
509 | "15492810 IS NOT PRIME ❌\n",
510 | "15492803 IS PRIME ✅\n",
511 | "15492787 IS PRIME ✅\n",
512 | "15502547 IS PRIME ✅\n",
513 | "15492859 IS PRIME ✅\n",
514 | "15492811 IS PRIME ✅\n",
515 | "15527509 IS PRIME ✅\n",
516 | "15492781 IS PRIME ✅\n",
517 | "15492833 IS PRIME ✅\n",
518 | "15520301 IS PRIME ✅\n"
519 | ]
520 | }
521 | ],
522 | "source": [
523 | "received = 0\n",
524 | "while received < 10:\n",
525 | " number, prime_result = main_conn.recv()\n",
526 | " received += 1\n",
527 | " if prime_result:\n",
528 | " print(f'{number} IS PRIME ✅')\n",
529 | " else:\n",
530 | " print(f'{number} IS NOT PRIME ❌')"
531 | ]
532 | },
533 | {
534 | "cell_type": "markdown",
535 | "metadata": {},
536 | "source": [
537 | "## Process Pools\n",
538 | "\n",
539 | "The `multiprocessing` module contains a very useful "
540 | ]
541 | },
542 | {
543 | "cell_type": "code",
544 | "execution_count": 61,
545 | "metadata": {},
546 | "outputs": [
547 | {
548 | "name": "stdout",
549 | "output_type": "stream",
550 | "text": [
551 | "Number 15492803 is prime\n",
552 | "Number 15492810 is NOT prime\n"
553 | ]
554 | }
555 | ],
556 | "source": [
557 | "with mp.Pool(processes=4) as pool:\n",
558 | " n = random.choice(numbers)\n",
559 | " result = pool.apply_async(is_prime, (n, ))\n",
560 | " print(f\"Number {n} {'is prime' if result.get() else 'is NOT prime'}\")\n",
561 | " \n",
562 | " n = random.choice(numbers)\n",
563 | " result = pool.apply_async(is_prime, (n, ))\n",
564 | " print(f\"Number {n} {'is prime' if result.get() else 'is NOT prime'}\")"
565 | ]
566 | },
567 | {
568 | "cell_type": "markdown",
569 | "metadata": {},
570 | "source": [
571 | "As you can see, we're using our regular `is_prime` function. This is important, as there seems to be some \"data sharing\" behind the scenes, which simplifies the API. The `apply_async` function submits a \"task\" to perform, it's computed behind the scenes and an `AsyncResult` is returned.\n",
572 | "\n",
573 | "Pools have other useful methods, like for example `map` and `map_async` (and other variants like `imap` or `starmap`). The `map` method is similar to the `map` builtin function, or a list comprehension. Let's see an example to process all our prime numbers:"
574 | ]
575 | },
576 | {
577 | "cell_type": "code",
578 | "execution_count": 44,
579 | "metadata": {},
580 | "outputs": [],
581 | "source": [
582 | "start = time.time()"
583 | ]
584 | },
585 | {
586 | "cell_type": "code",
587 | "execution_count": 45,
588 | "metadata": {},
589 | "outputs": [
590 | {
591 | "name": "stdout",
592 | "output_type": "stream",
593 | "text": [
594 | "Found 9 primes for a total of 10\n"
595 | ]
596 | }
597 | ],
598 | "source": [
599 | "with mp.Pool(processes=10) as pool:\n",
600 | " results = pool.map(is_prime, numbers)\n",
601 | " print(f\"Found {sum(results)} primes for a total of {len(results)}\")"
602 | ]
603 | },
604 | {
605 | "cell_type": "code",
606 | "execution_count": 46,
607 | "metadata": {},
608 | "outputs": [
609 | {
610 | "data": {
611 | "text/plain": [
612 | "0.7575781345367432"
613 | ]
614 | },
615 | "execution_count": 46,
616 | "metadata": {},
617 | "output_type": "execute_result"
618 | }
619 | ],
620 | "source": [
621 | "time.time() - start"
622 | ]
623 | },
624 | {
625 | "cell_type": "markdown",
626 | "metadata": {},
627 | "source": [
628 | "The `map` interface is very convenient, it feels like a regular synchronous Python API, but behind the scenes is using a pool of multiple processes. In our next lesson, when we talk about `concurrent.futures` we'll see why familiar and intuitive interfaces make our life easier.\n",
629 | "\n",
630 | "\n",
631 | "## Summary\n",
632 | "\n",
633 | "In this lesson we're just scratching the surface of multiprocessing work. Sadly, working with multiple processes is a lot harder than using threads, as it requires a deeper understanding of the operating system and it's a lot less safe."
634 | ]
635 | }
636 | ],
637 | "metadata": {
638 | "kernelspec": {
639 | "display_name": "Python 3",
640 | "language": "python",
641 | "name": "python3"
642 | },
643 | "language_info": {
644 | "codemirror_mode": {
645 | "name": "ipython",
646 | "version": 3
647 | },
648 | "file_extension": ".py",
649 | "mimetype": "text/x-python",
650 | "name": "python",
651 | "nbconvert_exporter": "python",
652 | "pygments_lexer": "ipython3",
653 | "version": "3.8.0"
654 | }
655 | },
656 | "nbformat": 4,
657 | "nbformat_minor": 4
658 | }
659 |
--------------------------------------------------------------------------------
/7. concurrent.futures.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 53,
6 | "metadata": {},
7 | "outputs": [],
8 | "source": [
9 | "import queue\n",
10 | "import multiprocessing as mp\n",
11 | "import concurrent.futures as cf\n",
12 | "\n",
13 | "from queue import Queue, SimpleQueue\n",
14 | "from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor\n",
15 | "\n",
16 | "from datetime import datetime, timedelta\n",
17 | "\n",
18 | "import requests"
19 | ]
20 | },
21 | {
22 | "cell_type": "markdown",
23 | "metadata": {},
24 | "source": [
25 | "# `concurrent.futures`\n",
26 | "\n",
27 | "This lesson has a strange name. `concurrent.futures` is the name of a (relative) modern package in the Python standard library. It's a package with a beautiful and Pythonic API that abstracts us from the low level mechanisms of concurrency.\n",
28 | "\n",
29 | "**`concurrent.futures` should be your default choice for concurrent programming as much as possible**\n",
30 | "\n",
31 | "In this tutorial, we started from the low levels `threading` and `multiprocessing` because we wanted to explain the concepts behind concurrency, but `concurrent.futures` offers a much safer and intuitive API. Let's start with it.\n",
32 | "\n",
33 | "## Executors and futures\n",
34 | "\n",
35 | "#### Executors\n",
36 | "Executors are the entry points of `cf`. They are similar to `multiprocessing.Pool`s. Once an executor has been instantiated, we can `submit` jobs, or even `map` tasks, similar to `multiprocessin.Pool.map`. `concurrent.futures.Executor` is an abstract class. `cf` includes two concrete classes: `ThreadPoolExecutor` and `ProcessPoolExecutor`. This means that we can keep the same interface, but use completely different mechanisms just by changing the executor type we're using:"
37 | ]
38 | },
39 | {
40 | "cell_type": "code",
41 | "execution_count": 14,
42 | "metadata": {},
43 | "outputs": [],
44 | "source": [
45 | "def check_price(exchange, symbol, date):\n",
46 | " base_url = \"http://localhost:5000\"\n",
47 | " resp = requests.get(f\"{base_url}/price/{exchange}/{symbol}/{date}\")\n",
48 | " return resp.json()"
49 | ]
50 | },
51 | {
52 | "cell_type": "code",
53 | "execution_count": 15,
54 | "metadata": {},
55 | "outputs": [
56 | {
57 | "name": "stdout",
58 | "output_type": "stream",
59 | "text": [
60 | "Price: $6421.14\n"
61 | ]
62 | }
63 | ],
64 | "source": [
65 | "with ThreadPoolExecutor(max_workers=10) as ex:\n",
66 | " future = ex.submit(check_price, 'bitstamp', 'btc', '2020-04-01')\n",
67 | " print(f\"Price: ${future.result()['close']}\")"
68 | ]
69 | },
70 | {
71 | "cell_type": "code",
72 | "execution_count": 16,
73 | "metadata": {},
74 | "outputs": [
75 | {
76 | "name": "stdout",
77 | "output_type": "stream",
78 | "text": [
79 | "Price: $6421.14\n"
80 | ]
81 | }
82 | ],
83 | "source": [
84 | "with ProcessPoolExecutor(max_workers=10, mp_context=mp.get_context('fork')) as ex:\n",
85 | " future = ex.submit(check_price, 'bitstamp', 'btc', '2020-04-01')\n",
86 | " print(f\"Price: ${future.result()['close']}\")"
87 | ]
88 | },
89 | {
90 | "cell_type": "markdown",
91 | "metadata": {},
92 | "source": [
93 | "This is the beauty of `cf`: we're using the same logic with two completely different executors; the API is the same.\n",
94 | "\n",
95 | "#### Futures\n",
96 | "\n",
97 | "As you can see from the the examples above, the `submit` method returns immediately a `Future` object. These objects are an abstraction of a task that is being processed. They have multiple useful methods that we can use (as seen in the following example). The most important one, `result(timeout=None)` will block for `timeout` seconds until a result was produced:"
98 | ]
99 | },
100 | {
101 | "cell_type": "code",
102 | "execution_count": 18,
103 | "metadata": {},
104 | "outputs": [
105 | {
106 | "name": "stdout",
107 | "output_type": "stream",
108 | "text": [
109 | "False\n",
110 | "Price: $6421.14\n",
111 | "True\n"
112 | ]
113 | }
114 | ],
115 | "source": [
116 | "with ThreadPoolExecutor(max_workers=10) as ex:\n",
117 | " future = ex.submit(check_price, 'bitstamp', 'btc', '2020-04-01')\n",
118 | " print(future.done())\n",
119 | " print(f\"Price: ${future.result()['close']}\")\n",
120 | " print(future.done())"
121 | ]
122 | },
123 | {
124 | "cell_type": "markdown",
125 | "metadata": {},
126 | "source": [
127 | "#### The `map` method\n",
128 | "\n",
129 | "Executors have a `map` method that is similar to `mp.Pool.map`, it's convenient as there are no futures to work with, but it's limited as only one parameter can be passed:"
130 | ]
131 | },
132 | {
133 | "cell_type": "code",
134 | "execution_count": 19,
135 | "metadata": {},
136 | "outputs": [],
137 | "source": [
138 | "EXCHANGES = ['bitfinex', 'bitstamp', 'kraken']"
139 | ]
140 | },
141 | {
142 | "cell_type": "code",
143 | "execution_count": 20,
144 | "metadata": {},
145 | "outputs": [],
146 | "source": [
147 | "def check_price_tuple(arg):\n",
148 | " exchange, symbol, date = arg\n",
149 | " base_url = \"http://localhost:5000\"\n",
150 | " resp = requests.get(f\"{base_url}/price/{exchange}/{symbol}/{date}\")\n",
151 | " return resp.json()"
152 | ]
153 | },
154 | {
155 | "cell_type": "code",
156 | "execution_count": 23,
157 | "metadata": {},
158 | "outputs": [
159 | {
160 | "name": "stdout",
161 | "output_type": "stream",
162 | "text": [
163 | "[6409.8, 6421.14, 6401.9]\n"
164 | ]
165 | }
166 | ],
167 | "source": [
168 | "with ThreadPoolExecutor(max_workers=10) as ex:\n",
169 | " results = ex.map(check_price_tuple, [\n",
170 | " (exchange, 'btc', '2020-04-01')\n",
171 | " for exchange in EXCHANGES\n",
172 | " ])\n",
173 | " print([price['close'] for price in results])"
174 | ]
175 | },
176 | {
177 | "cell_type": "code",
178 | "execution_count": 25,
179 | "metadata": {},
180 | "outputs": [
181 | {
182 | "data": {
183 | "text/plain": [
184 | "('bitstamp', 'btc', '2020-04-01')"
185 | ]
186 | },
187 | "execution_count": 25,
188 | "metadata": {},
189 | "output_type": "execute_result"
190 | }
191 | ],
192 | "source": [
193 | "('bitstamp', 'btc', '2020-04-01')"
194 | ]
195 | },
196 | {
197 | "cell_type": "markdown",
198 | "metadata": {},
199 | "source": [
200 | "As you can see, we had to define a new special function that works by receiving a tuple instead of the individual elements.\n",
201 | "\n",
202 | "#### `submit` & `as_completed` pattern\n",
203 | "\n",
204 | "To overcome the limitation of `Executor.map`, we can use a common pattern of creating multiple futures with `Executor.submit` and waiting for them to complete with the module-level function `concurrent.futures.as_completed`:"
205 | ]
206 | },
207 | {
208 | "cell_type": "code",
209 | "execution_count": 27,
210 | "metadata": {},
211 | "outputs": [
212 | {
213 | "name": "stdout",
214 | "output_type": "stream",
215 | "text": [
216 | "Kraken: $6401.9\n",
217 | "Bitfinex: $6409.8\n",
218 | "Bitstamp: $6421.14\n"
219 | ]
220 | }
221 | ],
222 | "source": [
223 | "with ThreadPoolExecutor(max_workers=10) as ex:\n",
224 | " futures = {\n",
225 | " ex.submit(check_price, exchange, 'btc', '2020-04-01'): exchange\n",
226 | " for exchange in EXCHANGES\n",
227 | " }\n",
228 | " for future in cf.as_completed(futures):\n",
229 | " exchange = futures[future]\n",
230 | " print(f\"{exchange.title()}: ${future.result()['close']}\")"
231 | ]
232 | },
233 | {
234 | "cell_type": "markdown",
235 | "metadata": {},
236 | "source": [
237 | "## Producer/Consumer with `concurrent.futures`\n",
238 | "\n",
239 | "I'll show you an example of the producer/consumer pattern using the `cf` module. There are multiple ways to create this pattern, I'll stick to the basics."
240 | ]
241 | },
242 | {
243 | "cell_type": "code",
244 | "execution_count": 37,
245 | "metadata": {},
246 | "outputs": [],
247 | "source": [
248 | "BASE_URL = \"http://localhost:5000\""
249 | ]
250 | },
251 | {
252 | "cell_type": "code",
253 | "execution_count": 38,
254 | "metadata": {},
255 | "outputs": [],
256 | "source": [
257 | "resp = requests.get(f\"{BASE_URL}/exchanges\")"
258 | ]
259 | },
260 | {
261 | "cell_type": "code",
262 | "execution_count": 39,
263 | "metadata": {},
264 | "outputs": [
265 | {
266 | "data": {
267 | "text/plain": [
268 | "['bitfinex', 'bitstamp', 'bittrex']"
269 | ]
270 | },
271 | "execution_count": 39,
272 | "metadata": {},
273 | "output_type": "execute_result"
274 | }
275 | ],
276 | "source": [
277 | "EXCHANGES = resp.json()\n",
278 | "EXCHANGES[:3]"
279 | ]
280 | },
281 | {
282 | "cell_type": "code",
283 | "execution_count": 40,
284 | "metadata": {},
285 | "outputs": [],
286 | "source": [
287 | "START_DATE = datetime(2020, 3, 1)"
288 | ]
289 | },
290 | {
291 | "cell_type": "code",
292 | "execution_count": 41,
293 | "metadata": {},
294 | "outputs": [],
295 | "source": [
296 | "DATES = [(START_DATE + timedelta(days=i)).strftime('%Y-%m-%d') for i in range(31)]"
297 | ]
298 | },
299 | {
300 | "cell_type": "code",
301 | "execution_count": 42,
302 | "metadata": {},
303 | "outputs": [
304 | {
305 | "data": {
306 | "text/plain": [
307 | "['2020-03-01', '2020-03-02', '2020-03-03']"
308 | ]
309 | },
310 | "execution_count": 42,
311 | "metadata": {},
312 | "output_type": "execute_result"
313 | }
314 | ],
315 | "source": [
316 | "DATES[:3]"
317 | ]
318 | },
319 | {
320 | "cell_type": "code",
321 | "execution_count": 43,
322 | "metadata": {},
323 | "outputs": [],
324 | "source": [
325 | "resp = requests.get(f\"{BASE_URL}/symbols\")"
326 | ]
327 | },
328 | {
329 | "cell_type": "code",
330 | "execution_count": 44,
331 | "metadata": {},
332 | "outputs": [
333 | {
334 | "data": {
335 | "text/plain": [
336 | "['btc', 'eth', 'ltc']"
337 | ]
338 | },
339 | "execution_count": 44,
340 | "metadata": {},
341 | "output_type": "execute_result"
342 | }
343 | ],
344 | "source": [
345 | "SYMBOLS = resp.json()\n",
346 | "SYMBOLS"
347 | ]
348 | },
349 | {
350 | "cell_type": "markdown",
351 | "metadata": {},
352 | "source": [
353 | "Queues:"
354 | ]
355 | },
356 | {
357 | "cell_type": "code",
358 | "execution_count": 85,
359 | "metadata": {},
360 | "outputs": [],
361 | "source": [
362 | "work_to_do = Queue()\n",
363 | "work_done = SimpleQueue()"
364 | ]
365 | },
366 | {
367 | "cell_type": "code",
368 | "execution_count": 86,
369 | "metadata": {},
370 | "outputs": [],
371 | "source": [
372 | "for exchange in EXCHANGES:\n",
373 | " for date in DATES:\n",
374 | " for symbol in SYMBOLS:\n",
375 | " task = {\n",
376 | " 'exchange': exchange,\n",
377 | " 'symbol': symbol,\n",
378 | " 'date': date,\n",
379 | " }\n",
380 | " work_to_do.put(task)"
381 | ]
382 | },
383 | {
384 | "cell_type": "code",
385 | "execution_count": 87,
386 | "metadata": {},
387 | "outputs": [
388 | {
389 | "data": {
390 | "text/plain": [
391 | "1023"
392 | ]
393 | },
394 | "execution_count": 87,
395 | "metadata": {},
396 | "output_type": "execute_result"
397 | }
398 | ],
399 | "source": [
400 | "work_to_do.qsize()"
401 | ]
402 | },
403 | {
404 | "cell_type": "code",
405 | "execution_count": 88,
406 | "metadata": {},
407 | "outputs": [],
408 | "source": [
409 | "def worker(task_queue, results_queue):\n",
410 | " while True:\n",
411 | " try:\n",
412 | " task = task_queue.get(block=False)\n",
413 | " except queue.Empty:\n",
414 | " print('Queue is empty! My work here is done. Exiting.')\n",
415 | " return\n",
416 | " exchange, symbol, date = task['exchange'], task['symbol'], task['date']\n",
417 | " price = check_price(exchange, symbol, date)\n",
418 | " results_queue.put((price, exchange, symbol, date))\n",
419 | " task_queue.task_done()"
420 | ]
421 | },
422 | {
423 | "cell_type": "code",
424 | "execution_count": 89,
425 | "metadata": {},
426 | "outputs": [
427 | {
428 | "name": "stdout",
429 | "output_type": "stream",
430 | "text": [
431 | "Queue is empty! My work here is done. Exiting.\n",
432 | "Queue is empty! My work here is done. Exiting.\n",
433 | "Queue is empty! My work here is done. Exiting.Queue is empty! My work here is done. Exiting.\n",
434 | "\n",
435 | "Queue is empty! My work here is done. Exiting.\n",
436 | "Queue is empty! My work here is done. Exiting.\n",
437 | "Queue is empty! My work here is done. Exiting.Queue is empty! My work here is done. Exiting.\n",
438 | "\n",
439 | "Queue is empty! My work here is done. Exiting.\n",
440 | "Queue is empty! My work here is done. Exiting.\n",
441 | "Queue is empty! My work here is done. Exiting.\n",
442 | "Queue is empty! My work here is done. Exiting.\n",
443 | "Queue is empty! My work here is done. Exiting.\n",
444 | "Queue is empty! My work here is done. Exiting.\n",
445 | "Queue is empty! My work here is done. Exiting.\n",
446 | "Queue is empty! My work here is done. Exiting.\n",
447 | "Queue is empty! My work here is done. Exiting.Queue is empty! My work here is done. Exiting.\n",
448 | "\n",
449 | "Queue is empty! My work here is done. Exiting.\n",
450 | "Queue is empty! My work here is done. Exiting.\n",
451 | "Queue is empty! My work here is done. Exiting.\n",
452 | "Queue is empty! My work here is done. Exiting.\n",
453 | "Queue is empty! My work here is done. Exiting.\n",
454 | "Queue is empty! My work here is done. Exiting.\n",
455 | "Queue is empty! My work here is done. Exiting.\n",
456 | "Queue is empty! My work here is done. Exiting.\n",
457 | "Queue is empty! My work here is done. Exiting.Queue is empty! My work here is done. Exiting.\n",
458 | "\n",
459 | "Queue is empty! My work here is done. Exiting.\n",
460 | "Queue is empty! My work here is done. Exiting.\n",
461 | "Queue is empty! My work here is done. Exiting.\n",
462 | "Queue is empty! My work here is done. Exiting.\n"
463 | ]
464 | }
465 | ],
466 | "source": [
467 | "with ThreadPoolExecutor(max_workers=32) as ex:\n",
468 | " futures = [\n",
469 | " ex.submit(worker, work_to_do, work_done) for _ in range(32)\n",
470 | " ]\n",
471 | " work_to_do.join()"
472 | ]
473 | },
474 | {
475 | "cell_type": "code",
476 | "execution_count": 90,
477 | "metadata": {},
478 | "outputs": [
479 | {
480 | "data": {
481 | "text/plain": [
482 | "True"
483 | ]
484 | },
485 | "execution_count": 90,
486 | "metadata": {},
487 | "output_type": "execute_result"
488 | }
489 | ],
490 | "source": [
491 | "all([f.done() for f in futures])"
492 | ]
493 | },
494 | {
495 | "cell_type": "code",
496 | "execution_count": 91,
497 | "metadata": {},
498 | "outputs": [
499 | {
500 | "data": {
501 | "text/plain": [
502 | "1023"
503 | ]
504 | },
505 | "execution_count": 91,
506 | "metadata": {},
507 | "output_type": "execute_result"
508 | }
509 | ],
510 | "source": [
511 | "work_done.qsize()"
512 | ]
513 | },
514 | {
515 | "cell_type": "code",
516 | "execution_count": 92,
517 | "metadata": {},
518 | "outputs": [],
519 | "source": [
520 | "results = {}"
521 | ]
522 | },
523 | {
524 | "cell_type": "code",
525 | "execution_count": 93,
526 | "metadata": {},
527 | "outputs": [],
528 | "source": [
529 | "while True:\n",
530 | " try:\n",
531 | " price, exchange, symbol, date = work_done.get(block=None)\n",
532 | " results.setdefault(exchange, {})\n",
533 | " results[exchange].setdefault(date, {})\n",
534 | " results[exchange][date][symbol] = price['close'] if price else None\n",
535 | " except queue.Empty:\n",
536 | " break"
537 | ]
538 | },
539 | {
540 | "cell_type": "code",
541 | "execution_count": 95,
542 | "metadata": {},
543 | "outputs": [
544 | {
545 | "data": {
546 | "text/plain": [
547 | "7941"
548 | ]
549 | },
550 | "execution_count": 95,
551 | "metadata": {},
552 | "output_type": "execute_result"
553 | }
554 | ],
555 | "source": [
556 | "results['bitfinex']['2020-03-10']['btc']"
557 | ]
558 | },
559 | {
560 | "cell_type": "code",
561 | "execution_count": 96,
562 | "metadata": {},
563 | "outputs": [
564 | {
565 | "data": {
566 | "text/plain": [
567 | "7936.25"
568 | ]
569 | },
570 | "execution_count": 96,
571 | "metadata": {},
572 | "output_type": "execute_result"
573 | }
574 | ],
575 | "source": [
576 | "results['bitstamp']['2020-03-10']['btc']"
577 | ]
578 | },
579 | {
580 | "cell_type": "code",
581 | "execution_count": 98,
582 | "metadata": {},
583 | "outputs": [
584 | {
585 | "data": {
586 | "text/plain": [
587 | "7934.52"
588 | ]
589 | },
590 | "execution_count": 98,
591 | "metadata": {},
592 | "output_type": "execute_result"
593 | }
594 | ],
595 | "source": [
596 | "results['coinbase-pro']['2020-03-10']['btc']"
597 | ]
598 | },
599 | {
600 | "cell_type": "markdown",
601 | "metadata": {},
602 | "source": [
603 | "## Summary\n",
604 | "\n",
605 | "The `concurrent.futures` module is the most abstract, highest level concurrency module in the Python standard library and **it SHOULD be your default option** when writing concurrent code. Only if you need more advanced capabilities, you should use the `threading` or `multiprocessing` modules directly."
606 | ]
607 | }
608 | ],
609 | "metadata": {
610 | "kernelspec": {
611 | "display_name": "Python 3",
612 | "language": "python",
613 | "name": "python3"
614 | },
615 | "language_info": {
616 | "codemirror_mode": {
617 | "name": "ipython",
618 | "version": 3
619 | },
620 | "file_extension": ".py",
621 | "mimetype": "text/x-python",
622 | "name": "python",
623 | "nbconvert_exporter": "python",
624 | "pygments_lexer": "ipython3",
625 | "version": "3.8.0"
626 | }
627 | },
628 | "nbformat": 4,
629 | "nbformat_minor": 4
630 | }
631 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Santiago Basulto
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 | # Concurrency and Parallelism for beginners
2 | > PyCon 2020 Tutorial 👉 [\[slides\]](https://docs.google.com/presentation/d/1VdBEtXK5A8nFjIfGCSBR8pc6g-zv0yBGuIPBfffOo_k/edit?usp=sharing)
3 |
4 | This is the code for my PyCon 2020 online tutorial about concurrency and parallelism using Python
5 |
6 | This tutorial provides a complete overview of what is concurrency (and parallelism) and how to create concurrent programs using multithreading and multiprocessing with Python. I'm also focused on including all the important background information required to understand concurrency, for example: Computer Architecture, Operating Systems concepts, etc.
7 |
8 | #### How to use this code
9 |
10 | Just fire up your favorite Jupyter Notebook server (jupyterlab recommended) and load the notebooks in sequence. The recorded video includes a step by step explanation of each notebook, but it should be possible to follow just from the code.
11 |
--------------------------------------------------------------------------------
/crypto-examples/Create Database.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 1,
6 | "metadata": {},
7 | "outputs": [],
8 | "source": [
9 | "import sqlite3\n",
10 | "from pathlib import Path\n",
11 | "import pandas as pd"
12 | ]
13 | },
14 | {
15 | "cell_type": "code",
16 | "execution_count": 2,
17 | "metadata": {},
18 | "outputs": [],
19 | "source": [
20 | "SCHEMA = '''\n",
21 | "\n",
22 | "drop table if exists trip_update;\n",
23 | "create table trip_update (\n",
24 | " id integer primary key autoincrement,\n",
25 | " update_timestamp DATETIME not null,\n",
26 | " trip_id int not null\n",
27 | ");\n",
28 | "\n",
29 | "drop table if exists trip;\n",
30 | "create table trip (\n",
31 | " id integer primary key autoincrement,\n",
32 | "\n",
33 | " service_id int not null,\n",
34 | " trip_headsign text\n",
35 | ");\n",
36 | "'''"
37 | ]
38 | },
39 | {
40 | "cell_type": "markdown",
41 | "metadata": {},
42 | "source": [
43 | "### Prune DB"
44 | ]
45 | },
46 | {
47 | "cell_type": "code",
48 | "execution_count": 4,
49 | "metadata": {},
50 | "outputs": [],
51 | "source": [
52 | "conn = sqlite3.connect('prices.db')"
53 | ]
54 | },
55 | {
56 | "cell_type": "code",
57 | "execution_count": 5,
58 | "metadata": {},
59 | "outputs": [
60 | {
61 | "data": {
62 | "text/plain": [
63 | ""
64 | ]
65 | },
66 | "execution_count": 5,
67 | "metadata": {},
68 | "output_type": "execute_result"
69 | }
70 | ],
71 | "source": [
72 | "conn.executescript(SCHEMA)"
73 | ]
74 | },
75 | {
76 | "cell_type": "code",
77 | "execution_count": 6,
78 | "metadata": {},
79 | "outputs": [],
80 | "source": [
81 | "conn.commit()"
82 | ]
83 | },
84 | {
85 | "cell_type": "code",
86 | "execution_count": 7,
87 | "metadata": {},
88 | "outputs": [],
89 | "source": [
90 | "conn.close()"
91 | ]
92 | },
93 | {
94 | "cell_type": "markdown",
95 | "metadata": {},
96 | "source": [
97 | "### Insert Data"
98 | ]
99 | },
100 | {
101 | "cell_type": "code",
102 | "execution_count": 8,
103 | "metadata": {},
104 | "outputs": [],
105 | "source": [
106 | "BASE_PATH = Path('crypto_data/')"
107 | ]
108 | },
109 | {
110 | "cell_type": "code",
111 | "execution_count": 9,
112 | "metadata": {},
113 | "outputs": [],
114 | "source": [
115 | "files = list(BASE_PATH.glob('*.csv'))"
116 | ]
117 | },
118 | {
119 | "cell_type": "code",
120 | "execution_count": 11,
121 | "metadata": {},
122 | "outputs": [],
123 | "source": [
124 | "INSERT_STATEMENT = \"\"\"\n",
125 | "INSERT INTO price (\n",
126 | " exchange, symbol, open, high, low, close, volume, day\n",
127 | ") VALUES (?, ?, ?, ?, ?, ?, ?, ?);\n",
128 | "\"\"\""
129 | ]
130 | },
131 | {
132 | "cell_type": "code",
133 | "execution_count": 12,
134 | "metadata": {},
135 | "outputs": [],
136 | "source": [
137 | "conn = sqlite3.connect('prices.db')"
138 | ]
139 | },
140 | {
141 | "cell_type": "code",
142 | "execution_count": 13,
143 | "metadata": {},
144 | "outputs": [],
145 | "source": [
146 | "for file in files:\n",
147 | " exchange, symbol = file.name[:-4].split('_')\n",
148 | " df = pd.read_csv(str(file))\n",
149 | " df['exchange'] = exchange\n",
150 | " df['symbol'] = symbol\n",
151 | " \n",
152 | " values = df[['exchange', 'symbol', 'OpenPrice', 'HighPrice', 'LowPrice', 'ClosePrice', 'Volume', 'DateTime']].values\n",
153 | " conn.executemany(INSERT_STATEMENT, values)\n",
154 | " conn.commit()"
155 | ]
156 | },
157 | {
158 | "cell_type": "code",
159 | "execution_count": 14,
160 | "metadata": {},
161 | "outputs": [],
162 | "source": [
163 | "conn.close()"
164 | ]
165 | },
166 | {
167 | "cell_type": "markdown",
168 | "metadata": {},
169 | "source": [
170 | "### Final Test"
171 | ]
172 | },
173 | {
174 | "cell_type": "code",
175 | "execution_count": 15,
176 | "metadata": {},
177 | "outputs": [],
178 | "source": [
179 | "conn = sqlite3.connect('prices.db')"
180 | ]
181 | },
182 | {
183 | "cell_type": "code",
184 | "execution_count": 16,
185 | "metadata": {},
186 | "outputs": [],
187 | "source": [
188 | "cursor = conn.cursor()"
189 | ]
190 | },
191 | {
192 | "cell_type": "code",
193 | "execution_count": 17,
194 | "metadata": {},
195 | "outputs": [
196 | {
197 | "data": {
198 | "text/plain": [
199 | ""
200 | ]
201 | },
202 | "execution_count": 17,
203 | "metadata": {},
204 | "output_type": "execute_result"
205 | }
206 | ],
207 | "source": [
208 | "cursor.execute('SELECT COUNT(*) FROM price;')"
209 | ]
210 | },
211 | {
212 | "cell_type": "code",
213 | "execution_count": 18,
214 | "metadata": {},
215 | "outputs": [
216 | {
217 | "data": {
218 | "text/plain": [
219 | "(14421,)"
220 | ]
221 | },
222 | "execution_count": 18,
223 | "metadata": {},
224 | "output_type": "execute_result"
225 | }
226 | ],
227 | "source": [
228 | "cursor.fetchone()"
229 | ]
230 | },
231 | {
232 | "cell_type": "code",
233 | "execution_count": 19,
234 | "metadata": {},
235 | "outputs": [
236 | {
237 | "data": {
238 | "text/plain": [
239 | ""
240 | ]
241 | },
242 | "execution_count": 19,
243 | "metadata": {},
244 | "output_type": "execute_result"
245 | }
246 | ],
247 | "source": [
248 | "cursor.execute('SELECT * FROM price LIMIT 5;')"
249 | ]
250 | },
251 | {
252 | "cell_type": "code",
253 | "execution_count": 20,
254 | "metadata": {},
255 | "outputs": [
256 | {
257 | "data": {
258 | "text/plain": [
259 | "[(1, 'okcoin', 'ltc', 218.5, 233.53, 213.39, 219.965, 6543390, '2018-02-27'),\n",
260 | " (2,\n",
261 | " 'okcoin',\n",
262 | " 'ltc',\n",
263 | " 219.987,\n",
264 | " 225.51,\n",
265 | " 213.237,\n",
266 | " 215.865,\n",
267 | " 4382590,\n",
268 | " '2018-02-28'),\n",
269 | " (3,\n",
270 | " 'okcoin',\n",
271 | " 'ltc',\n",
272 | " 216.255,\n",
273 | " 227.27700000000002,\n",
274 | " 201.976,\n",
275 | " 203.09400000000002,\n",
276 | " 5222322,\n",
277 | " '2018-03-01'),\n",
278 | " (4,\n",
279 | " 'okcoin',\n",
280 | " 'ltc',\n",
281 | " 203.09400000000002,\n",
282 | " 219.238,\n",
283 | " 200.207,\n",
284 | " 213.87,\n",
285 | " 3697634,\n",
286 | " '2018-03-02'),\n",
287 | " (5,\n",
288 | " 'okcoin',\n",
289 | " 'ltc',\n",
290 | " 214.06599999999997,\n",
291 | " 217.5,\n",
292 | " 205.812,\n",
293 | " 214.173,\n",
294 | " 3217448,\n",
295 | " '2018-03-03')]"
296 | ]
297 | },
298 | "execution_count": 20,
299 | "metadata": {},
300 | "output_type": "execute_result"
301 | }
302 | ],
303 | "source": [
304 | "cursor.fetchall()"
305 | ]
306 | },
307 | {
308 | "cell_type": "code",
309 | "execution_count": 21,
310 | "metadata": {},
311 | "outputs": [],
312 | "source": [
313 | "conn.close()"
314 | ]
315 | },
316 | {
317 | "cell_type": "markdown",
318 | "metadata": {},
319 | "source": [
320 | "### Exchanges"
321 | ]
322 | },
323 | {
324 | "cell_type": "code",
325 | "execution_count": 22,
326 | "metadata": {},
327 | "outputs": [],
328 | "source": [
329 | "conn = sqlite3.connect('prices.db')"
330 | ]
331 | },
332 | {
333 | "cell_type": "code",
334 | "execution_count": 23,
335 | "metadata": {},
336 | "outputs": [],
337 | "source": [
338 | "cursor = conn.cursor()"
339 | ]
340 | },
341 | {
342 | "cell_type": "code",
343 | "execution_count": 24,
344 | "metadata": {},
345 | "outputs": [
346 | {
347 | "data": {
348 | "text/plain": [
349 | ""
350 | ]
351 | },
352 | "execution_count": 24,
353 | "metadata": {},
354 | "output_type": "execute_result"
355 | }
356 | ],
357 | "source": [
358 | "cursor.execute('SELECT DISTINCT exchange FROM price;')"
359 | ]
360 | },
361 | {
362 | "cell_type": "code",
363 | "execution_count": 25,
364 | "metadata": {},
365 | "outputs": [
366 | {
367 | "data": {
368 | "text/plain": [
369 | "[('okcoin',),\n",
370 | " ('hitbtc',),\n",
371 | " ('mexbt',),\n",
372 | " ('kraken',),\n",
373 | " ('okex',),\n",
374 | " ('bittrex',),\n",
375 | " ('bitstamp',),\n",
376 | " ('bitfinex',),\n",
377 | " ('coinbase-pro',),\n",
378 | " ('poloniex',),\n",
379 | " ('cexio',),\n",
380 | " ('huobi',)]"
381 | ]
382 | },
383 | "execution_count": 25,
384 | "metadata": {},
385 | "output_type": "execute_result"
386 | }
387 | ],
388 | "source": [
389 | "cursor.fetchall()"
390 | ]
391 | },
392 | {
393 | "cell_type": "code",
394 | "execution_count": 26,
395 | "metadata": {},
396 | "outputs": [
397 | {
398 | "data": {
399 | "text/plain": [
400 | ""
401 | ]
402 | },
403 | "execution_count": 26,
404 | "metadata": {},
405 | "output_type": "execute_result"
406 | }
407 | ],
408 | "source": [
409 | "cursor.execute('SELECT DISTINCT symbol FROM price;')"
410 | ]
411 | },
412 | {
413 | "cell_type": "code",
414 | "execution_count": 27,
415 | "metadata": {},
416 | "outputs": [
417 | {
418 | "data": {
419 | "text/plain": [
420 | "[('ltc',), ('btc',), ('eth',)]"
421 | ]
422 | },
423 | "execution_count": 27,
424 | "metadata": {},
425 | "output_type": "execute_result"
426 | }
427 | ],
428 | "source": [
429 | "cursor.fetchall()"
430 | ]
431 | },
432 | {
433 | "cell_type": "markdown",
434 | "metadata": {},
435 | "source": [
436 | "### Filtered query:"
437 | ]
438 | },
439 | {
440 | "cell_type": "code",
441 | "execution_count": 28,
442 | "metadata": {},
443 | "outputs": [
444 | {
445 | "data": {
446 | "text/plain": [
447 | ""
448 | ]
449 | },
450 | "execution_count": 28,
451 | "metadata": {},
452 | "output_type": "execute_result"
453 | }
454 | ],
455 | "source": [
456 | "cursor.execute('SELECT * FROM price WHERE symbol = \"btc\" AND exchange = \"bitfinex\" AND day = \"2019-07-20\";')"
457 | ]
458 | },
459 | {
460 | "cell_type": "code",
461 | "execution_count": 29,
462 | "metadata": {},
463 | "outputs": [
464 | {
465 | "data": {
466 | "text/plain": [
467 | "[(3396,\n",
468 | " 'bitfinex',\n",
469 | " 'btc',\n",
470 | " 10661,\n",
471 | " 10751,\n",
472 | " 10115,\n",
473 | " 10516,\n",
474 | " 387.16246975,\n",
475 | " '2019-07-20')]"
476 | ]
477 | },
478 | "execution_count": 29,
479 | "metadata": {},
480 | "output_type": "execute_result"
481 | }
482 | ],
483 | "source": [
484 | "cursor.fetchall()"
485 | ]
486 | }
487 | ],
488 | "metadata": {
489 | "kernelspec": {
490 | "display_name": "Python 3",
491 | "language": "python",
492 | "name": "python3"
493 | },
494 | "language_info": {
495 | "codemirror_mode": {
496 | "name": "ipython",
497 | "version": 3
498 | },
499 | "file_extension": ".py",
500 | "mimetype": "text/x-python",
501 | "name": "python",
502 | "nbconvert_exporter": "python",
503 | "pygments_lexer": "ipython3",
504 | "version": "3.8.0"
505 | }
506 | },
507 | "nbformat": 4,
508 | "nbformat_minor": 4
509 | }
510 |
--------------------------------------------------------------------------------
/crypto-examples/Insert in Database.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 1,
6 | "metadata": {},
7 | "outputs": [
8 | {
9 | "name": "stderr",
10 | "output_type": "stream",
11 | "text": [
12 | "/Users/santiagobasulto/Library/Caches/pypoetry/virtualenvs/pycon-concurrency-tutorial-2020-a5tTVfGc-py3.8/lib/python3.8/site-packages/pandas/compat/__init__.py:117: UserWarning: Could not import the lzma module. Your installed Python is incomplete. Attempting to use lzma compression will result in a RuntimeError.\n",
13 | " warnings.warn(msg)\n"
14 | ]
15 | }
16 | ],
17 | "source": [
18 | "import sqlite3\n",
19 | "from pathlib import Path\n",
20 | "import pandas as pd"
21 | ]
22 | },
23 | {
24 | "cell_type": "code",
25 | "execution_count": 2,
26 | "metadata": {},
27 | "outputs": [],
28 | "source": [
29 | "SCHEMA = '''\n",
30 | "\n",
31 | "drop table if exists price;\n",
32 | "create table price (\n",
33 | " id integer primary key autoincrement,\n",
34 | " exchange text not null,\n",
35 | " symbol text not null,\n",
36 | " open DECIMAL(10, 4),\n",
37 | " high DECIMAL(10, 4),\n",
38 | " low DECIMAL(10, 4),\n",
39 | " close DECIMAL(10, 4),\n",
40 | " volume DECIMAL(10, 4),\n",
41 | " day DATE not null\n",
42 | ");\n",
43 | "'''"
44 | ]
45 | },
46 | {
47 | "cell_type": "markdown",
48 | "metadata": {},
49 | "source": [
50 | "### Prune DB"
51 | ]
52 | },
53 | {
54 | "cell_type": "code",
55 | "execution_count": 3,
56 | "metadata": {},
57 | "outputs": [],
58 | "source": [
59 | "conn = sqlite3.connect('prices.db')"
60 | ]
61 | },
62 | {
63 | "cell_type": "code",
64 | "execution_count": 4,
65 | "metadata": {},
66 | "outputs": [
67 | {
68 | "data": {
69 | "text/plain": [
70 | ""
71 | ]
72 | },
73 | "execution_count": 4,
74 | "metadata": {},
75 | "output_type": "execute_result"
76 | }
77 | ],
78 | "source": [
79 | "conn.executescript(SCHEMA)"
80 | ]
81 | },
82 | {
83 | "cell_type": "code",
84 | "execution_count": 5,
85 | "metadata": {},
86 | "outputs": [],
87 | "source": [
88 | "conn.commit()"
89 | ]
90 | },
91 | {
92 | "cell_type": "code",
93 | "execution_count": 6,
94 | "metadata": {},
95 | "outputs": [],
96 | "source": [
97 | "conn.close()"
98 | ]
99 | },
100 | {
101 | "cell_type": "markdown",
102 | "metadata": {},
103 | "source": [
104 | "### Insert Data"
105 | ]
106 | },
107 | {
108 | "cell_type": "code",
109 | "execution_count": 7,
110 | "metadata": {},
111 | "outputs": [],
112 | "source": [
113 | "BASE_PATH = Path('crypto_data/')"
114 | ]
115 | },
116 | {
117 | "cell_type": "code",
118 | "execution_count": 8,
119 | "metadata": {},
120 | "outputs": [],
121 | "source": [
122 | "files = list(BASE_PATH.glob('*.csv'))"
123 | ]
124 | },
125 | {
126 | "cell_type": "code",
127 | "execution_count": 9,
128 | "metadata": {},
129 | "outputs": [],
130 | "source": [
131 | "INSERT_STATEMENT = \"\"\"\n",
132 | "INSERT INTO price (\n",
133 | " exchange, symbol, open, high, low, close, volume, day\n",
134 | ") VALUES (?, ?, ?, ?, ?, ?, ?, ?);\n",
135 | "\"\"\""
136 | ]
137 | },
138 | {
139 | "cell_type": "code",
140 | "execution_count": 10,
141 | "metadata": {},
142 | "outputs": [],
143 | "source": [
144 | "conn = sqlite3.connect('prices.db')"
145 | ]
146 | },
147 | {
148 | "cell_type": "code",
149 | "execution_count": 11,
150 | "metadata": {},
151 | "outputs": [],
152 | "source": [
153 | "for file in files:\n",
154 | " exchange, symbol = file.name[:-4].split('_')\n",
155 | " df = pd.read_csv(str(file))\n",
156 | " df['exchange'] = exchange\n",
157 | " df['symbol'] = symbol\n",
158 | " \n",
159 | " values = df[['exchange', 'symbol', 'OpenPrice', 'HighPrice', 'LowPrice', 'ClosePrice', 'Volume', 'DateTime']].values\n",
160 | " conn.executemany(INSERT_STATEMENT, values)\n",
161 | " conn.commit()"
162 | ]
163 | },
164 | {
165 | "cell_type": "code",
166 | "execution_count": 12,
167 | "metadata": {},
168 | "outputs": [],
169 | "source": [
170 | "conn.close()"
171 | ]
172 | },
173 | {
174 | "cell_type": "markdown",
175 | "metadata": {},
176 | "source": [
177 | "### Final Test"
178 | ]
179 | },
180 | {
181 | "cell_type": "code",
182 | "execution_count": 13,
183 | "metadata": {},
184 | "outputs": [],
185 | "source": [
186 | "conn = sqlite3.connect('prices.db')"
187 | ]
188 | },
189 | {
190 | "cell_type": "code",
191 | "execution_count": 14,
192 | "metadata": {},
193 | "outputs": [],
194 | "source": [
195 | "cursor = conn.cursor()"
196 | ]
197 | },
198 | {
199 | "cell_type": "code",
200 | "execution_count": 15,
201 | "metadata": {},
202 | "outputs": [
203 | {
204 | "data": {
205 | "text/plain": [
206 | ""
207 | ]
208 | },
209 | "execution_count": 15,
210 | "metadata": {},
211 | "output_type": "execute_result"
212 | }
213 | ],
214 | "source": [
215 | "cursor.execute('SELECT COUNT(*) FROM price;')"
216 | ]
217 | },
218 | {
219 | "cell_type": "code",
220 | "execution_count": 16,
221 | "metadata": {},
222 | "outputs": [
223 | {
224 | "data": {
225 | "text/plain": [
226 | "(28008,)"
227 | ]
228 | },
229 | "execution_count": 16,
230 | "metadata": {},
231 | "output_type": "execute_result"
232 | }
233 | ],
234 | "source": [
235 | "cursor.fetchone()"
236 | ]
237 | },
238 | {
239 | "cell_type": "code",
240 | "execution_count": 17,
241 | "metadata": {},
242 | "outputs": [
243 | {
244 | "data": {
245 | "text/plain": [
246 | ""
247 | ]
248 | },
249 | "execution_count": 17,
250 | "metadata": {},
251 | "output_type": "execute_result"
252 | }
253 | ],
254 | "source": [
255 | "cursor.execute('SELECT * FROM price LIMIT 5;')"
256 | ]
257 | },
258 | {
259 | "cell_type": "code",
260 | "execution_count": 18,
261 | "metadata": {},
262 | "outputs": [
263 | {
264 | "data": {
265 | "text/plain": [
266 | "[(1,\n",
267 | " 'hitbtc',\n",
268 | " 'ltc',\n",
269 | " 167.625,\n",
270 | " 173.372,\n",
271 | " 167.09099999999998,\n",
272 | " 173.313,\n",
273 | " 1893.8,\n",
274 | " '2018-03-24'),\n",
275 | " (2,\n",
276 | " 'hitbtc',\n",
277 | " 'ltc',\n",
278 | " 173.264,\n",
279 | " 173.77900000000002,\n",
280 | " 162.202,\n",
281 | " 162.668,\n",
282 | " 14864.014,\n",
283 | " '2018-03-25'),\n",
284 | " (3,\n",
285 | " 'hitbtc',\n",
286 | " 'ltc',\n",
287 | " 162.60399999999998,\n",
288 | " 167.28,\n",
289 | " 160.417,\n",
290 | " 163.946,\n",
291 | " 14783.375,\n",
292 | " '2018-03-26'),\n",
293 | " (4,\n",
294 | " 'hitbtc',\n",
295 | " 'ltc',\n",
296 | " 164.107,\n",
297 | " 164.69400000000002,\n",
298 | " 145.002,\n",
299 | " 151.275,\n",
300 | " 17593.473,\n",
301 | " '2018-03-27'),\n",
302 | " (5,\n",
303 | " 'hitbtc',\n",
304 | " 'ltc',\n",
305 | " 151.224,\n",
306 | " 152.44299999999998,\n",
307 | " 137.503,\n",
308 | " 137.561,\n",
309 | " 20596.676,\n",
310 | " '2018-03-28')]"
311 | ]
312 | },
313 | "execution_count": 18,
314 | "metadata": {},
315 | "output_type": "execute_result"
316 | }
317 | ],
318 | "source": [
319 | "cursor.fetchall()"
320 | ]
321 | },
322 | {
323 | "cell_type": "code",
324 | "execution_count": 19,
325 | "metadata": {},
326 | "outputs": [],
327 | "source": [
328 | "conn.close()"
329 | ]
330 | },
331 | {
332 | "cell_type": "markdown",
333 | "metadata": {},
334 | "source": [
335 | "### Exchanges"
336 | ]
337 | },
338 | {
339 | "cell_type": "code",
340 | "execution_count": 20,
341 | "metadata": {},
342 | "outputs": [],
343 | "source": [
344 | "conn = sqlite3.connect('prices.db')"
345 | ]
346 | },
347 | {
348 | "cell_type": "code",
349 | "execution_count": 21,
350 | "metadata": {},
351 | "outputs": [],
352 | "source": [
353 | "cursor = conn.cursor()"
354 | ]
355 | },
356 | {
357 | "cell_type": "code",
358 | "execution_count": 22,
359 | "metadata": {},
360 | "outputs": [
361 | {
362 | "data": {
363 | "text/plain": [
364 | ""
365 | ]
366 | },
367 | "execution_count": 22,
368 | "metadata": {},
369 | "output_type": "execute_result"
370 | }
371 | ],
372 | "source": [
373 | "cursor.execute('SELECT DISTINCT exchange FROM price;')"
374 | ]
375 | },
376 | {
377 | "cell_type": "code",
378 | "execution_count": 23,
379 | "metadata": {},
380 | "outputs": [
381 | {
382 | "data": {
383 | "text/plain": [
384 | "[('hitbtc',),\n",
385 | " ('mexbt',),\n",
386 | " ('kraken',),\n",
387 | " ('okex',),\n",
388 | " ('bittrex',),\n",
389 | " ('bitstamp',),\n",
390 | " ('bitfinex',),\n",
391 | " ('coinbase-pro',),\n",
392 | " ('poloniex',),\n",
393 | " ('cexio',),\n",
394 | " ('huobi',)]"
395 | ]
396 | },
397 | "execution_count": 23,
398 | "metadata": {},
399 | "output_type": "execute_result"
400 | }
401 | ],
402 | "source": [
403 | "cursor.fetchall()"
404 | ]
405 | },
406 | {
407 | "cell_type": "code",
408 | "execution_count": 24,
409 | "metadata": {},
410 | "outputs": [
411 | {
412 | "data": {
413 | "text/plain": [
414 | ""
415 | ]
416 | },
417 | "execution_count": 24,
418 | "metadata": {},
419 | "output_type": "execute_result"
420 | }
421 | ],
422 | "source": [
423 | "cursor.execute('SELECT DISTINCT symbol FROM price;')"
424 | ]
425 | },
426 | {
427 | "cell_type": "code",
428 | "execution_count": 25,
429 | "metadata": {},
430 | "outputs": [
431 | {
432 | "data": {
433 | "text/plain": [
434 | "[('ltc',), ('btc',), ('eth',)]"
435 | ]
436 | },
437 | "execution_count": 25,
438 | "metadata": {},
439 | "output_type": "execute_result"
440 | }
441 | ],
442 | "source": [
443 | "cursor.fetchall()"
444 | ]
445 | },
446 | {
447 | "cell_type": "markdown",
448 | "metadata": {},
449 | "source": [
450 | "### Filtered query:"
451 | ]
452 | },
453 | {
454 | "cell_type": "code",
455 | "execution_count": 26,
456 | "metadata": {},
457 | "outputs": [
458 | {
459 | "data": {
460 | "text/plain": [
461 | ""
462 | ]
463 | },
464 | "execution_count": 26,
465 | "metadata": {},
466 | "output_type": "execute_result"
467 | }
468 | ],
469 | "source": [
470 | "cursor.execute('SELECT * FROM price WHERE symbol = \"btc\" AND exchange = \"bitfinex\" AND day = \"2019-07-20\";')"
471 | ]
472 | },
473 | {
474 | "cell_type": "code",
475 | "execution_count": 27,
476 | "metadata": {},
477 | "outputs": [
478 | {
479 | "data": {
480 | "text/plain": [
481 | "[(6064,\n",
482 | " 'bitfinex',\n",
483 | " 'btc',\n",
484 | " 10661,\n",
485 | " 10751,\n",
486 | " 10115,\n",
487 | " 10516,\n",
488 | " 387.16246975,\n",
489 | " '2019-07-20')]"
490 | ]
491 | },
492 | "execution_count": 27,
493 | "metadata": {},
494 | "output_type": "execute_result"
495 | }
496 | ],
497 | "source": [
498 | "cursor.fetchall()"
499 | ]
500 | }
501 | ],
502 | "metadata": {
503 | "kernelspec": {
504 | "display_name": "Python 3",
505 | "language": "python",
506 | "name": "python3"
507 | },
508 | "language_info": {
509 | "codemirror_mode": {
510 | "name": "ipython",
511 | "version": 3
512 | },
513 | "file_extension": ".py",
514 | "mimetype": "text/x-python",
515 | "name": "python",
516 | "nbconvert_exporter": "python",
517 | "pygments_lexer": "ipython3",
518 | "version": "3.8.0"
519 | }
520 | },
521 | "nbformat": 4,
522 | "nbformat_minor": 4
523 | }
524 |
--------------------------------------------------------------------------------
/crypto-examples/crypto_data/cexio_btc.csv:
--------------------------------------------------------------------------------
1 | CloseTime,OpenPrice,HighPrice,LowPrice,ClosePrice,Volume,VolumeUSD,DateTime
2 | 1571356800,8045.2,8104.0,8000.8,8044.0,0.90460703,7275.051652097,2019-10-18
3 | 1571443200,8067.5,8067.5,7831.1,7965.1,0.42207363,3356.249260433,2019-10-19
4 | 1571529600,7920.0,8051.6,7918.2,7934.9,1.76663665,14070.233498329,2019-10-20
5 | 1571616000,7940.0,8229.5,7875.3,8197.3,2.01864172,16035.868286486,2019-10-21
6 | 1571702400,8199.8,8350.0,8174.5,8213.5,3.99224649,32798.511011175,2019-10-22
7 | 1571788800,8171.4,8279.8,8126.6,8130.2,0.97635963,8042.179440284,2019-10-23
8 | 1571875200,8068.8,8068.8,7299.4,7444.0,2.32189058,17697.354835233,2019-10-24
9 | 1571961600,7423.9,7475.0,7000.0,7416.4,0.77037818,5716.168259236,2019-10-25
10 | 1572048000,7423.2,8770.4,7402.0,8618.9,5.34294564,43779.932849434,2019-10-26
11 | 1572134400,8690.8,10080.1,8405.2,9227.2,27.3614942,251798.481067705,2019-10-27
12 | 1572220800,9234.2,9739.0,9093.1,9551.7,3.77744863,35467.827831512,2019-10-28
13 | 1572307200,9607.0,9890.6,9205.4,9243.6,2.06328752,19577.143781889,2019-10-29
14 | 1572393600,9280.9,9491.7,9070.4,9386.0,2.58390436,24166.645961281,2019-10-30
15 | 1572480000,9384.7,9430.9,9016.2,9183.1,2.45442976,22637.00905911,2019-10-31
16 | 1572566400,9210.4,9386.7,8965.8,9162.2,2.46566022,22576.086190078,2019-11-01
17 | 1572652800,9139.7,9229.5,9057.5,9181.1,7.54927973,68657.590344668,2019-11-02
18 | 1572739200,9217.3,9331.3,9175.0,9278.8,0.75426282,6977.731468677,2019-11-03
19 | 1572825600,9306.4,9344.8,9107.0,9107.0,0.92123281,8446.254468297,2019-11-04
20 | 1572912000,9195.3,9545.3,9195.3,9435.4,0.51994176,4871.13319788,2019-11-05
21 | 1572998400,9379.6,9408.9,9185.9,9312.7,0.97687818,9052.266007014,2019-11-06
22 | 1573084800,9290.7,9400.1,9257.4,9367.5,0.42519256,3969.973693888,2019-11-07
23 | 1573171200,9320.5,9335.5,9076.8,9196.8,0.78807605,7261.637400927,2019-11-08
24 | 1573257600,9156.8,9168.3,8776.1,8806.6,22.54038497,202777.447179994,2019-11-09
25 | 1573344000,8808.8,8906.1,8772.7,8834.5,0.84076188,7439.651223484,2019-11-10
26 | 1573430400,8856.1,9116.8,8798.7,9116.8,1.22334687,10986.300562166,2019-11-11
27 | 1573516800,9074.3,9078.5,8677.8,8738.9,0.77770104,6819.39983328,2019-11-12
28 | 1573603200,8755.0,8867.6,8662.4,8756.5,0.94629782,8299.994490921,2019-11-13
29 | 1573689600,8792.8,8809.3,8747.3,8789.2,0.3458798,3039.5276922,2019-11-14
30 | 1573776000,8783.4,8795.7,8608.4,8689.7,2.10181944,18208.547487053,2019-11-15
31 | 1573862400,8567.9,8799.2,8457.7,8486.4,1.04104834,8951.499137978,2019-11-16
32 | 1573948800,8452.0,8534.4,8431.4,8502.3,0.60551615,5144.877348247,2019-11-17
33 | 1574035200,8519.9,8599.2,8400.1,8520.4,0.71214647,6067.398629533,2019-11-18
34 | 1574121600,8509.0,8525.5,8116.6,8228.4,2.11380046,17803.411507314,2019-11-19
35 | 1574208000,8168.8,8183.0,8035.5,8093.7,0.39599629,3207.125811523,2019-11-20
36 | 1574294400,8139.2,8236.2,8095.1,8127.0,0.55301812,4506.213521701,2019-11-21
37 | 1574380800,8127.1,10100.0,7546.6,7633.0,4.22647021,34382.466285818,2019-11-22
38 | 1574467200,7651.0,7691.7,6835.2,7238.7,6.15725526,44148.814113373,2019-11-23
39 | 1574553600,7223.9,7339.7,7152.2,7268.2,0.71736932,5189.765277733,2019-11-24
40 | 1574640000,7319.7,7319.7,6857.9,6885.1,1.9587877,13927.982883582,2019-11-25
41 | 1574726400,6917.8,7266.2,6522.1,7132.9,9.01077019,61498.340136114,2019-11-26
42 | 1574812800,7152.2,7313.9,7069.2,7156.2,3.25895505,23543.797361242,2019-11-27
43 | 1574899200,7155.9,7638.6,6862.7,7526.4,5.39301999,38583.032290411,2019-11-28
44 | 1574985600,7488.7,7625.1,7333.6,7333.6,3.03051467,22661.77322072,2019-11-29
45 | 1575072000,7397.6,7823.2,7397.6,7735.7,1.79176187,13672.62481206,2019-11-30
46 | 1575158400,7766.3,7766.3,7453.0,7536.7,3.56390225,26851.655777004,2019-12-01
47 | 1575244800,7499.0,7499.0,7265.9,7432.7,5.09793722,37518.914965153,2019-12-02
48 | 1575331200,7377.4,7406.8,7234.2,7293.7,3.68873017,27120.152073924,2019-12-03
49 | 1575417600,7273.0,7340.4,7107.8,7306.3,3.14047695,22723.000596454,2019-12-04
50 | 1575504000,7291.2,7793.2,7110.8,7235.9,6.25274956,46875.721558355,2019-12-05
51 | 1575590400,7188.9,7452.9,7188.9,7420.7,2.40243327,17731.582206538,2019-12-06
52 | 1575676800,7367.1,7573.4,7367.1,7533.5,1.32861266,9942.027773606,2019-12-07
53 | 1575763200,7568.8,7599.7,7500.0,7522.0,1.32489639,10004.410293219,2019-12-08
54 | 1575849600,7500.2,7568.9,7399.0,7546.2,4.77798319,35575.591600466,2019-12-09
55 | 1575936000,7531.0,7607.9,7325.0,7344.4,1.77432085,13302.126103356,2019-12-10
56 | 1576022400,7394.9,7394.9,7196.0,7221.1,0.73261436,5303.357024606,2019-12-11
57 | 1576108800,7232.8,7264.0,7174.8,7226.8,1.86614268,13476.4370162,2019-12-12
58 | 1576195200,7279.5,7279.5,7150.0,7219.1,0.43440899,3120.274451989,2019-12-13
59 | 1576281600,7236.2,7264.0,7236.2,7244.2,0.10519072,762.751655876,2019-12-14
60 | 1576368000,7244.3,7244.3,7079.7,7093.1,0.10790746,774.356598547,2019-12-15
61 | 1576454400,7097.4,7179.2,7074.5,7093.3,0.26858179,1911.554567179,2019-12-16
62 | 1576540800,7102.1,7112.6,6865.8,6889.7,0.64601158,4488.472428144,2019-12-17
63 | 1576627200,6867.6,6896.1,6589.4,6619.8,2.32036822,15606.68785775,2019-12-18
64 | 1576713600,6654.2,7439.8,6469.4,7305.9,3.99363282,27931.029978688,2019-12-19
65 | 1576800000,7233.8,7238.0,7094.4,7147.7,1.03885065,7459.526996447,2019-12-20
66 | 1576886400,7143.0,7246.1,7129.0,7209.9,1.34031267,9646.821121845,2019-12-21
67 | 1576972800,7163.2,7181.0,7142.8,7181.0,1.28690323,9224.615709855,2019-12-22
68 | 1577059200,7167.0,7502.8,7167.0,7502.8,1.65432472,11973.523738787,2019-12-23
69 | 1577145600,7515.0,7697.8,7330.0,7363.3,3.1469805,23590.769061955,2019-12-24
70 | 1577232000,7335.5,7439.9,7231.6,7294.1,4.98368391,36684.924889884,2019-12-25
71 | 1577318400,7255.7,7277.9,7185.3,7241.8,0.5308062,3846.00494272,2019-12-26
72 | 1577404800,7199.8,7433.2,7199.8,7228.1,0.92288263,6739.874184003,2019-12-27
73 | 1577491200,7266.6,7281.3,7166.9,7281.3,0.9346538,6767.41035958,2019-12-28
74 | 1577577600,7325.1,7364.0,7309.3,7362.0,1.17883893,8655.108298597,2019-12-29
75 | 1577664000,7315.1,7508.8,7310.3,7481.4,0.73242333,5431.33707392,2019-12-30
76 | 1577750400,7392.1,7417.9,7276.0,7301.4,0.934705,6846.170178572,2019-12-31
77 | 1577836800,7266.2,7325.0,7209.6,7223.9,0.09816827,713.11027679,2020-01-01
78 | 1577923200,7266.3,7268.3,7233.1,7233.1,0.44042684,3194.342645164,2020-01-02
79 | 1578009600,7163.7,7209.6,6973.9,6987.5,1.15283415,8182.607660635,2020-01-03
80 | 1578096000,6978.7,7396.8,6904.3,7334.6,4.78825549,34840.270334392,2020-01-04
81 | 1578182400,7369.0,7385.8,7317.1,7385.8,0.33255624,2447.955310513,2020-01-05
82 | 1578268800,7386.0,7482.0,7386.0,7409.8,1.24789889,9290.634689609,2020-01-06
83 | 1578355200,7450.0,7800.0,7450.0,7779.7,1.43724496,10935.377337876,2020-01-07
84 | 1578441600,7800.0,8143.6,7767.2,8143.6,1.77232589,13930.861785245,2020-01-08
85 | 1578528000,8316.2,8420.6,7894.8,8026.3,1.13326823,9324.451394966,2020-01-09
86 | 1578614400,7987.6,7989.8,7800.2,7860.1,1.06928308,8436.759229511,2020-01-10
87 | 1578700800,7819.8,8147.7,7769.6,8147.7,0.41455287,3313.21854406,2020-01-11
88 | 1578787200,8193.5,8265.5,8045.4,8055.0,1.50419018,12263.667952144,2020-01-12
89 | 1578873600,7999.0,8215.3,7987.0,8215.3,1.06958948,8692.308470499,2020-01-13
90 | 1578960000,8115.0,8167.3,8115.0,8145.0,0.34344143,2792.404793858,2020-01-14
91 | 1579046400,8172.4,8889.8,8172.4,8889.8,4.39390906,38131.381584151,2020-01-15
92 | 1579132800,8891.1,8906.1,8604.0,8818.1,4.4832049,39078.394791557,2020-01-16
93 | 1579219200,8700.0,8791.2,8627.6,8763.0,1.83880077,16019.917055268,2020-01-17
94 | 1579305600,8694.1,8991.8,8300.0,8935.8,2.37217117,21036.063968548,2020-01-18
95 | 1579392000,8912.2,8985.4,8814.3,8934.0,2.70811596,24175.423399072,2020-01-19
96 | 1579478400,8940.8,9112.6,8504.3,8705.2,4.0193831,35212.300122901,2020-01-20
97 | 1579564800,8675.2,8735.0,8545.8,8735.0,0.52229882,4521.652995972,2020-01-21
98 | 1579651200,8688.1,8708.3,8546.3,8686.3,1.46355359,12677.850240299,2020-01-22
99 | 1579737600,8744.0,8775.9,8656.0,8656.0,2.03606311,17701.123104711,2020-01-23
100 | 1579824000,8567.4,8590.0,8326.1,8386.3,0.82185056,6941.069002636,2020-01-24
101 | 1579910400,8324.2,8688.3,8274.0,8450.1,4.96781124,42252.862292369,2020-01-25
102 | 1579996800,8450.0,8450.0,8302.1,8404.4,0.63555445,5337.784809761,2020-01-26
103 | 1580083200,8334.9,8590.0,8334.9,8590.0,0.9403379,8011.631466787,2020-01-27
104 | 1580169600,8663.0,9033.1,8631.2,8945.8,1.30092945,11461.783000847,2020-01-28
105 | 1580256000,8998.3,9390.0,8958.1,9390.0,1.8816144,17148.114940468,2020-01-29
106 | 1580342400,9397.7,9443.7,9283.7,9353.6,0.63472196,5930.951959154,2020-01-30
107 | 1580428800,9242.3,9564.2,9242.3,9564.2,1.63112172,15424.962839481,2020-01-31
108 | 1580515200,9549.4,9556.0,8400.0,9383.5,2.78672879,25811.919697948,2020-02-01
109 | 1580601600,9395.4,9455.0,9299.0,9393.5,0.78265288,7351.574639794,2020-02-02
110 | 1580688000,9354.2,9465.0,9207.0,9380.7,0.69279157,6497.465913272,2020-02-03
111 | 1580774400,9325.2,9581.3,9278.0,9300.0,1.68016023,15714.191477567,2020-02-04
112 | 1580860800,9300.0,9363.0,9130.0,9224.0,2.04441433,18958.300145145,2020-02-05
113 | 1580947200,9230.1,9750.8,9230.1,9696.9,2.53203594,24242.110945723,2020-02-06
114 | 1581033600,9672.7,9899.9,9628.1,9756.0,1.76452094,17226.597126775,2020-02-07
115 | 1581120000,9849.4,9883.6,9748.3,9833.1,1.11533164,10948.949499534,2020-02-08
116 | 1581206400,9811.0,9975.7,9729.9,9975.7,1.19274112,11775.984841003,2020-02-09
117 | 1581292800,9964.5,10179.3,9940.0,10149.0,8.19098951,82540.678476742,2020-02-10
118 | 1581379200,10155.0,10181.8,9759.6,9796.0,3.21825692,31849.852225762,2020-02-11
119 | 1581465600,9842.7,10305.0,9707.1,10200.0,4.41866049,44482.311133774,2020-02-12
120 | 1581552000,10290.9,10413.4,10222.2,10331.0,2.70377131,27863.970145398,2020-02-13
121 | 1581638400,10414.2,10458.1,10100.0,10224.7,16.7204495,171624.128715842,2020-02-14
122 | 1581724800,10226.7,10360.0,10130.0,10335.3,1.27785336,13110.019924236,2020-02-15
123 | 1581811200,10295.6,10295.6,9806.0,9901.3,3.84091384,38571.082721424,2020-02-16
124 | 1581897600,9861.3,9976.4,9617.2,9968.7,4.94443614,48085.639508464,2020-02-17
125 | 1581984000,9823.8,9823.8,9498.9,9652.2,17.02815535,163206.601913368,2020-02-18
126 | 1582070400,9791.4,10239.4,9657.1,10172.2,1.97519876,19621.020855176,2020-02-19
127 | 1582156800,10094.3,10222.8,9309.9,9604.2,7.63679165,75375.072760658,2020-02-20
128 | 1582243200,9580.0,9673.3,8888.0,9550.5,1.20323262,11502.102033612,2020-02-21
129 | 1582329600,9577.7,9731.0,9577.7,9677.1,3.07650893,29844.105651656,2020-02-22
130 | 1582416000,9624.9,9723.5,9597.1,9715.9,0.13724316,1325.870858727,2020-02-23
131 | 1582502400,9865.2,10001.9,9831.2,9935.0,0.45437013,4514.353376467,2020-02-24
132 | 1582588800,9914.9,9914.9,9533.8,9628.3,2.20564767,21351.807072219,2020-02-25
133 | 1582675200,9615.6,9633.9,9308.0,9390.2,0.91900614,8681.187014163,2020-02-26
134 | 1582761600,9339.0,9339.0,8658.4,8800.6,4.60044753,41503.913349678,2020-02-27
135 | 1582848000,8691.9,8963.3,8552.3,8765.2,6.76772499,59761.600596355,2020-02-28
136 | 1582934400,8838.3,8886.9,8492.8,8525.6,1.91554752,16563.401070096,2020-02-29
137 | 1583020800,8785.9,8822.0,8580.8,8614.1,4.54696086,39387.272864833,2020-03-01
138 | 1583107200,8667.7,8713.5,8467.6,8472.1,2.00487807,17243.627592987,2020-03-02
139 | 1583193600,8480.5,8979.4,8480.5,8979.4,2.04526242,18070.704748141,2020-03-03
140 | 1583280000,8861.1,8889.2,8652.3,8736.4,2.30655701,20142.964373305,2020-03-04
141 | 1583366400,8833.9,8868.7,8670.9,8716.9,2.53584618,22197.551592936,2020-03-05
142 | 1583452800,8867.8,9153.3,8867.8,9141.4,2.84785831,25945.851238503,2020-03-06
143 | 1583539200,9141.4,10147.5,8600.0,9047.9,4.00259306,36229.027087081,2020-03-07
144 | 1583625600,9140.5,9186.9,8902.1,8905.6,2.12939802,19231.783610693,2020-03-08
145 | 1583712000,8896.3,8896.3,8080.6,8080.6,2.77546676,23648.683388354,2020-03-09
146 | 1583798400,8067.9,8108.0,7683.3,7916.8,10.75476447,84639.487418965,2020-03-10
147 | 1583884800,7949.4,8133.1,7771.7,7895.4,2.24262292,17858.379604955,2020-03-11
148 | 1583971200,7881.5,7993.3,7655.0,7993.3,2.53552783,19864.213163422,2020-03-12
149 | 1584057600,7905.1,8100.0,4830.0,5744.1,14.43048756,95644.995613608,2020-03-13
150 | 1584144000,4525.1,9000.0,2960.2,5507.5,16.98278667,93039.963525993,2020-03-14
151 | 1584230400,5585.3,5798.5,5199.4,5199.4,8.29120172,45234.121495928,2020-03-15
152 | 1584316800,5157.8,5858.5,4810.6,5379.8,5.01371435,26801.532596582,2020-03-16
153 | 1584403200,5300.0,5327.4,4448.0,4868.9,8.43398277,41385.454292829,2020-03-17
154 | 1584489600,4972.9,5400.0,4972.9,5400.0,2.37550399,12371.744624432,2020-03-18
155 | 1584576000,5364.1,5387.6,4928.0,5380.0,5.11475414,26972.758619598,2020-03-19
156 | 1584662400,5420.0,6387.6,5283.5,6206.0,5.38768446,31808.298866697,2020-03-20
157 | 1584748800,6147.1,6898.9,5846.4,6147.5,7.54654257,48848.873227045,2020-03-21
158 | 1584835200,6088.5,6409.8,5915.3,6215.8,2.78024562,17124.756219216,2020-03-22
159 | 1584921600,6112.8,6370.1,5795.9,5795.9,4.55421846,27709.715112552,2020-03-23
160 | 1585008000,5776.3,6601.6,5718.1,6484.6,6.11905413,36788.447976944,2020-03-24
161 | 1585094400,6501.9,6813.6,6406.7,6756.8,5.02938137,33051.499907624,2020-03-25
162 | 1585180800,6688.1,6904.8,6478.2,6644.2,3.48980063,23051.898921581,2020-03-26
163 | 1585267200,6700.7,6741.6,6521.7,6699.5,2.16712209,14360.734189972,2020-03-27
164 | 1585353600,6788.2,6800.0,6291.0,6320.8,2.78427227,18016.851890476,2020-03-28
165 | 1585440000,6310.6,6310.6,6032.6,6237.0,3.21885267,19950.163798606,2020-03-29
166 | 1585526400,6185.5,6405.6,5865.0,5920.0,5.17938227,31347.505923021,2020-03-30
167 | 1585612800,5876.0,6584.6,5876.0,6433.8,6.16090197,38536.277940408,2020-03-31
168 | 1585699200,6427.1,6488.3,6334.5,6451.2,3.87973432,24897.423620111,2020-04-01
169 | 1585785600,6377.9,6688.2,5800.0,6688.2,12.23964793,76012.716863791,2020-04-02
170 | 1585872000,6688.3,7162.1,6584.6,6756.4,6.52591909,44002.921318703,2020-04-03
171 | 1585958400,6703.7,6982.0,6633.4,6723.7,5.42809435,36734.938397819,2020-04-04
172 | 1586044800,6748.3,7000.0,6692.1,6867.6,3.39398811,23187.835967694,2020-04-05
173 | 1586131200,6842.4,6842.4,6705.2,6789.3,1.2538356,8515.4559173,2020-04-06
174 | 1586217600,6835.3,7291.0,6835.3,7291.0,6.46533034,46021.564468084,2020-04-07
175 | 1586304000,7413.3,7413.3,5000.0,7210.0,5.27486162,36940.774727516,2020-04-08
176 | 1586390400,7168.2,7334.6,7132.4,7263.8,2.42240564,17565.195107327,2020-04-09
177 | 1586476800,7249.3,7301.7,7080.1,7240.7,1.41281433,10196.102894594,2020-04-10
178 | 1586563200,7086.4,7119.7,6726.5,6880.4,9.22860144,63281.333543925,2020-04-11
179 | 1586649600,6862.6,6929.0,6735.0,6862.9,1.35335189,9248.417080837,2020-04-12
180 | 1586736000,6844.9,7138.7,6750.8,6853.6,3.03885652,20891.185173178,2020-04-13
181 | 1586822400,6858.4,6858.4,6549.7,6797.8,3.53976005,23716.239372597,2020-04-14
182 | 1586908800,6796.3,6935.8,6724.4,6824.2,6.36546727,43333.978923931,2020-04-15
183 | 1586995200,6798.5,6913.7,6587.1,6620.1,5.30710198,35740.807227228,2020-04-16
184 | 1587081600,6562.9,7134.9,6496.8,7016.3,6.46506032,44735.741147331,2020-04-17
185 | 1587168000,7053.3,7106.5,6935.7,7014.3,3.31261284,23299.571625556,2020-04-18
186 | 1587254400,7031.9,7257.8,7003.6,7234.2,2.74770808,19571.589004361,2020-04-19
187 | 1587340800,7240.0,7240.0,7035.4,7117.2,2.39608365,17101.894393368,2020-04-20
188 | 1587427200,7073.9,7198.2,6759.1,6862.9,2.98707349,20784.810197762,2020-04-21
189 | 1587513600,6816.7,6930.9,6763.6,6837.2,11.6205328,79687.132367424,2020-04-22
190 | 1587600000,6851.9,7141.1,6819.5,7134.9,3.74972364,26243.811121746,2020-04-23
191 | 1587686400,7140.4,10000.0,7030.0,7485.0,12.51409984,94031.167387664,2020-04-24
192 | 1587772800,7486.9,7603.2,7388.4,7489.0,6.56237124,49038.43513484,2020-04-25
193 | 1587859200,7493.3,7685.0,7447.7,7536.8,2.55818234,19306.394193388,2020-04-26
194 | 1587945600,7534.7,7684.9,7494.9,7671.5,2.11739312,16079.370292018,2020-04-27
195 | 1588032000,7698.0,7777.3,7619.3,7777.3,4.80937734,37002.257516443,2020-04-28
196 | 1588118400,7758.3,7766.4,7663.0,7741.6,3.83800602,29621.093856439,2020-04-29
197 | 1588204800,7755.2,8791.4,7706.2,8649.0,14.17151816,114207.981834302,2020-04-30
198 |
--------------------------------------------------------------------------------
/crypto-examples/crypto_data/cexio_eth.csv:
--------------------------------------------------------------------------------
1 | CloseTime,OpenPrice,HighPrice,LowPrice,ClosePrice,Volume,VolumeUSD,DateTime
2 | 1571356800,175.55,178.32,175.55,176.95,26.126598,4612.29302091,2019-10-18
3 | 1571443200,176.71,176.71,168.16,174.09,53.890239,9308.0581605,2019-10-19
4 | 1571529600,171.71,174.94,170.29,170.29,23.692494,4118.44817565,2019-10-20
5 | 1571616000,170.37,175.61,169.94,174.88,14.293721,2478.229098,2019-10-21
6 | 1571702400,174.22,177.3,172.04,173.64,26.774898,4663.33384615,2019-10-22
7 | 1571788800,173.37,173.37,171.68,172.34,10.759841,1852.97678956,2019-10-23
8 | 1571875200,171.31,171.74,153.29,160.72,68.371689,11125.199258,2019-10-24
9 | 1571961600,159.58,162.71,159.42,160.55,13.179042,2115.28438029,2019-10-25
10 | 1572048000,161.09,187.29,160.86,180.93,85.898936,15298.2164631,2019-10-26
11 | 1572134400,183.06,189.22,172.0,179.65,180.712092,32819.50509456,2019-10-27
12 | 1572220800,181.19,188.24,177.13,184.29,75.864462,13842.09975163,2019-10-28
13 | 1572307200,184.08,189.03,180.75,181.78,34.209334,6362.71932109,2019-10-29
14 | 1572393600,182.39,191.98,182.39,190.56,56.825859,10652.62212668,2019-10-30
15 | 1572480000,190.6,190.6,180.28,183.12,62.813128,11738.07376189,2019-10-31
16 | 1572566400,184.78,184.78,177.92,181.1,34.592756,6282.10758931,2019-11-01
17 | 1572652800,181.86,183.03,178.0,183.03,85.66098,15392.79927873,2019-11-02
18 | 1572739200,182.57,184.63,181.07,181.93,17.047826,3119.07072947,2019-11-03
19 | 1572825600,182.79,183.91,180.0,180.0,9.653167,1755.05599176,2019-11-04
20 | 1572912000,180.6,187.84,180.41,185.23,7.450559,1377.04960544,2019-11-05
21 | 1572998400,185.12,191.04,182.43,188.49,13.52734,2525.7710613,2019-11-06
22 | 1573084800,187.25,192.32,187.25,190.28,10.839605,2060.88133061,2019-11-07
23 | 1573171200,188.24,188.24,183.51,183.83,13.082508,2420.37967184,2019-11-08
24 | 1573257600,182.45,185.48,181.66,185.48,191.816208,35023.671706,2019-11-09
25 | 1573344000,185.26,185.3,182.88,184.01,4.242265,782.27847854,2019-11-10
26 | 1573430400,183.54,191.31,183.54,189.29,25.211461,4748.48009764,2019-11-11
27 | 1573516800,188.55,189.01,184.99,185.79,13.69254,2549.30764115,2019-11-12
28 | 1573603200,185.41,187.06,182.3,186.32,36.86806,6858.65348385,2019-11-13
29 | 1573689600,185.38,189.32,185.38,187.8,18.265139,3427.72719678,2019-11-14
30 | 1573776000,186.54,186.54,183.81,184.72,11.988007,2217.48484901,2019-11-15
31 | 1573862400,180.66,185.95,179.05,179.46,30.420318,5527.63777673,2019-11-16
32 | 1573948800,179.57,183.27,179.06,182.91,21.528377,3924.05082779,2019-11-17
33 | 1574035200,185.04,185.04,184.31,184.63,14.955897,2762.39367534,2019-11-18
34 | 1574121600,183.08,183.08,175.54,178.05,14.556652,2581.76265842,2019-11-19
35 | 1574208000,177.51,177.51,173.33,173.87,9.117779,1592.84559559,2019-11-20
36 | 1574294400,176.68,177.08,173.76,175.18,25.215254,4446.63518338,2019-11-21
37 | 1574380800,175.4,175.4,131.33,160.04,98.93961,16064.19773671,2019-11-22
38 | 1574467200,161.05,161.05,138.78,148.26,64.252922,9635.75357187,2019-11-23
39 | 1574553600,149.66,153.36,146.73,152.87,47.858244,7232.7264801,2019-11-24
40 | 1574640000,149.31,150.48,139.66,139.66,76.450044,11395.52641076,2019-11-25
41 | 1574726400,140.55,149.73,134.04,147.15,292.257618,42593.88161841,2019-11-26
42 | 1574812800,145.82,150.07,145.82,148.41,21.221244,3151.37223686,2019-11-27
43 | 1574899200,145.76,156.27,142.52,153.96,24.245593,3634.70479494,2019-11-28
44 | 1574985600,153.72,153.9,151.08,151.08,46.126061,7025.56946186,2019-11-29
45 | 1575072000,152.23,156.4,152.23,154.21,18.456865,2844.75707746,2019-11-30
46 | 1575158400,154.58,154.58,152.74,152.74,2.041312,312.80191392,2019-12-01
47 | 1575244800,150.73,151.94,150.73,151.94,3.882771,586.92322574,2019-12-02
48 | 1575331200,150.31,150.31,147.5,149.42,23.316938,3456.86022589,2019-12-03
49 | 1575417600,132.14,148.52,132.14,147.63,105.91178,14774.88480836,2019-12-04
50 | 1575504000,144.17,152.67,144.17,146.14,102.954975,15506.36182135,2019-12-05
51 | 1575590400,143.93,148.07,143.93,147.58,15.036623,2210.37473179,2019-12-06
52 | 1575676800,147.22,149.36,146.54,148.39,8.753218,1294.68239062,2019-12-07
53 | 1575763200,149.22,149.22,147.16,147.61,2.096977,310.62808794,2019-12-08
54 | 1575849600,146.99,150.04,146.27,150.04,24.659075,3623.42521965,2019-12-09
55 | 1575936000,148.8,148.8,146.93,146.93,17.118151,2541.43417194,2019-12-10
56 | 1576022400,146.51,146.9,144.98,144.98,5.822847,849.65109387,2019-12-11
57 | 1576108800,145.44,145.8,142.86,142.86,3.574548,515.63077973,2019-12-12
58 | 1576195200,141.85,144.65,141.85,144.65,6.001192,863.54361612,2019-12-13
59 | 1576281600,144.09,144.85,143.55,144.8,44.516622,6439.68621029,2019-12-14
60 | 1576368000,144.73,145.09,140.79,142.33,58.119291,8207.72725088,2019-12-15
61 | 1576454400,140.47,143.0,140.47,143.0,1.292429,184.05553111,2019-12-16
62 | 1576540800,142.0,142.13,129.76,131.26,87.576673,12314.14313123,2019-12-17
63 | 1576627200,131.66,131.66,121.15,121.51,34.830397,4436.24960529,2019-12-18
64 | 1576713600,121.96,134.45,118.13,133.67,76.578997,9514.78781311,2019-12-19
65 | 1576800000,133.74,133.74,127.14,128.31,40.032103,5117.42281993,2019-12-20
66 | 1576886400,127.61,127.61,126.43,126.43,2.87,364.488465,2019-12-21
67 | 1576972800,127.53,128.37,127.53,127.8,66.265937,8491.27520536,2019-12-22
68 | 1577059200,127.72,131.91,127.72,131.91,5.444309,707.35109462,2019-12-23
69 | 1577145600,133.99,135.08,127.24,127.24,18.889835,2510.01844248,2019-12-24
70 | 1577232000,127.74,129.89,127.74,128.87,167.256766,21646.57247523,2019-12-25
71 | 1577318400,127.38,127.38,124.61,125.82,6.409771,808.51744411,2019-12-26
72 | 1577404800,126.13,127.47,125.73,126.47,140.926622,17845.20972652,2019-12-27
73 | 1577491200,126.07,127.32,123.72,126.7,59.401388,7477.57816251,2019-12-28
74 | 1577577600,127.47,129.11,127.47,129.11,2.669894,344.34538818,2019-12-29
75 | 1577664000,130.0,136.82,130.0,136.82,13.084055,1758.52215253,2019-12-30
76 | 1577750400,135.56,135.56,132.06,132.06,20.452952,2746.97344407,2019-12-31
77 | 1577836800,133.37,133.81,129.53,129.53,8.454622,1113.09571815,2020-01-01
78 | 1577923200,129.44,132.73,129.44,131.68,3.538628,461.30348353,2020-01-02
79 | 1578009600,130.33,130.71,127.28,127.28,14.803275,1929.2958841,2020-01-03
80 | 1578096000,130.06,134.49,129.12,134.49,16.087463,2128.01254317,2020-01-04
81 | 1578182400,132.82,135.3,132.82,133.78,9.40711,1257.09593577,2020-01-05
82 | 1578268800,135.72,137.83,135.72,137.83,32.305733,4430.27039913,2020-01-06
83 | 1578355200,138.62,143.0,138.51,143.0,7.252668,1019.71345056,2020-01-07
84 | 1578441600,144.84,144.84,142.46,143.89,4.945293,711.41597451,2020-01-08
85 | 1578528000,144.3,146.49,137.84,139.84,10.563811,1494.74657307,2020-01-09
86 | 1578614400,138.83,138.83,135.96,136.95,12.211952,1673.62529621,2020-01-10
87 | 1578700800,138.31,141.66,136.71,140.87,78.08061,10956.83950615,2020-01-11
88 | 1578787200,142.59,147.0,142.59,146.91,30.70895,4413.24264057,2020-01-12
89 | 1578873600,145.01,145.19,144.59,144.7,9.546363,1385.02862953,2020-01-13
90 | 1578960000,142.94,143.21,142.94,142.96,2.898204,414.45448684,2020-01-14
91 | 1579046400,146.87,170.77,146.87,165.21,107.77771,17222.51168062,2020-01-15
92 | 1579132800,168.75,171.41,162.44,166.4,44.525517,7401.00509026,2020-01-16
93 | 1579219200,166.59,166.59,158.62,163.91,22.711763,3691.18918029,2020-01-17
94 | 1579305600,165.01,173.3,165.01,172.47,139.677676,23516.11872936,2020-01-18
95 | 1579392000,172.5,178.06,168.87,174.79,36.696714,6330.02974679,2020-01-19
96 | 1579478400,174.49,176.36,163.74,166.06,46.161642,7829.94732116,2020-01-20
97 | 1579564800,165.92,167.22,163.69,167.21,10.247042,1702.44047096,2020-01-21
98 | 1579651200,166.49,168.61,166.49,166.7,13.947254,2331.73520717,2020-01-22
99 | 1579737600,171.16,171.16,166.55,167.93,8.147449,1374.25196806,2020-01-23
100 | 1579824000,165.2,165.2,160.79,160.79,8.010769,1309.39795986,2020-01-24
101 | 1579910400,159.35,163.15,157.37,161.58,37.120076,6003.64636987,2020-01-25
102 | 1580083200,160.74,165.8,160.74,165.8,34.064967,5587.11571429,2020-01-27
103 | 1580169600,166.96,171.62,166.95,171.49,16.409054,2775.24480514,2020-01-28
104 | 1580256000,172.0,175.8,170.93,175.44,66.15227,11503.54869343,2020-01-29
105 | 1580342400,176.71,177.22,175.07,176.48,12.874715,2272.46404155,2020-01-30
106 | 1580428800,175.0,184.68,174.68,184.68,28.867689,5113.18442176,2020-01-31
107 | 1580515200,182.64,182.64,178.19,180.41,14.594888,2624.50707929,2020-02-01
108 | 1580601600,181.92,183.6,180.0,183.1,7.394546,1351.21945326,2020-02-02
109 | 1580688000,184.83,192.06,184.83,191.87,25.724181,4918.85972162,2020-02-03
110 | 1580774400,187.95,193.94,186.53,190.32,30.024623,5742.4831848,2020-02-04
111 | 1580860800,188.33,188.33,185.91,188.12,117.327302,22038.76770408,2020-02-05
112 | 1580947200,189.2,203.7,189.06,202.73,27.443108,5409.78976185,2020-02-06
113 | 1581033600,210.18,214.29,206.41,213.55,23.90672,5011.14779648,2020-02-07
114 | 1581120000,216.43,223.91,216.1,222.39,38.687347,8538.19560223,2020-02-08
115 | 1581206400,220.0,227.22,220.0,221.77,163.540474,37057.04644468,2020-02-09
116 | 1581292800,229.74,229.74,224.85,224.85,17.700653,4024.05926365,2020-02-10
117 | 1581379200,219.95,224.22,216.69,223.95,78.302887,17214.02597016,2020-02-11
118 | 1581465600,218.65,237.77,218.09,236.03,63.24291,14619.14612013,2020-02-12
119 | 1581552000,238.46,274.23,238.46,267.41,122.575648,31173.48802749,2020-02-13
120 | 1581638400,269.29,276.62,257.34,264.05,66.884944,17947.23188082,2020-02-14
121 | 1581724800,267.55,284.75,260.87,284.75,68.196282,18524.85469439,2020-02-15
122 | 1581811200,286.35,286.76,262.59,267.38,105.955771,28985.29954412,2020-02-16
123 | 1581897600,260.22,273.34,237.29,261.4,63.208252,16205.4984472,2020-02-17
124 | 1581984000,256.36,265.0,243.39,265.0,53.008669,13463.57012577,2020-02-18
125 | 1582070400,270.61,284.0,259.58,281.65,55.4282,14967.96217734,2020-02-19
126 | 1582156800,280.05,281.4,249.0,257.56,71.07899,18750.52172615,2020-02-20
127 | 1582243200,259.69,260.0,248.06,258.61,42.129538,10814.23650912,2020-02-21
128 | 1582329600,253.94,265.85,253.94,265.85,2.944818,775.31241855,2020-02-22
129 | 1582416000,259.92,263.53,258.71,259.1,8.406028,2194.31794908,2020-02-23
130 | 1582502400,270.3,273.28,267.45,273.21,9.435919,2548.23560867,2020-02-24
131 | 1582588800,273.21,273.21,258.18,261.95,36.289307,9723.11753579,2020-02-25
132 | 1582675200,262.92,263.29,246.83,251.33,20.562132,5215.61440622,2020-02-26
133 | 1582761600,245.16,245.16,218.27,224.67,30.665022,7010.66195784,2020-02-27
134 | 1582848000,222.6,235.92,212.26,229.49,37.794104,8637.87366517,2020-02-28
135 | 1582934400,220.0,228.0,220.0,223.34,4.765078,1062.86452214,2020-02-29
136 | 1583020800,229.1,229.1,222.45,222.99,3.623543,810.29240351,2020-03-01
137 | 1583107200,218.28,226.96,213.9,219.09,15.501671,3356.05585502,2020-03-02
138 | 1583193600,217.94,233.22,217.94,232.34,10.057164,2276.20988659,2020-03-03
139 | 1583280000,227.73,229.94,221.35,224.98,31.125147,6968.83645162,2020-03-04
140 | 1583366400,222.99,225.24,220.3,223.93,19.186738,4285.76754708,2020-03-05
141 | 1583452800,225.65,234.32,225.65,228.61,46.504594,10758.27485571,2020-03-06
142 | 1583539200,232.53,243.66,232.53,243.66,29.34628,6976.02632777,2020-03-07
143 | 1583625600,246.58,251.94,238.0,238.0,53.277917,13050.68754426,2020-03-08
144 | 1583712000,228.6,231.68,200.08,200.08,65.856987,14183.71687436,2020-03-09
145 | 1583798400,201.43,208.27,192.16,201.61,130.878061,26183.10329927,2020-03-10
146 | 1583884800,201.57,205.25,197.63,198.87,30.178914,6046.04156505,2020-03-11
147 | 1583971200,199.13,499.0,182.95,194.41,164.073915,32515.35514132,2020-03-12
148 | 1584057600,189.97,189.97,26.0,105.23,396.224948,62646.10036121,2020-03-13
149 | 1584144000,107.56,137.45,100.01,134.72,273.862182,35192.09109626,2020-03-14
150 | 1584230400,132.9,133.63,124.02,124.02,76.150716,9911.82225982,2020-03-15
151 | 1584316800,123.17,129.35,120.65,125.5,127.546919,15728.31410034,2020-03-16
152 | 1584403200,116.74,116.74,102.87,110.23,110.868237,12267.19287295,2020-03-17
153 | 1584489600,110.09,119.63,110.09,119.63,9.52096,1118.99028493,2020-03-18
154 | 1584576000,115.54,117.53,112.76,116.0,23.656715,2732.47409309,2020-03-19
155 | 1584662400,118.88,141.8,118.88,137.0,63.302167,8113.7297535,2020-03-20
156 | 1584748800,137.0,149.93,116.7,132.52,108.223533,13923.17026976,2020-03-21
157 | 1584835200,133.47,136.86,127.28,134.79,15.103796,1993.23003993,2020-03-22
158 | 1584921600,133.08,133.08,123.02,123.02,58.471554,7479.6951518,2020-03-23
159 | 1585008000,122.1,133.71,121.99,129.49,59.245958,7574.26402698,2020-03-24
160 | 1585094400,135.47,141.26,135.47,139.17,28.535339,3938.64350789,2020-03-25
161 | 1585180800,139.9,139.9,133.92,135.68,13.737886,1853.06077552,2020-03-26
162 | 1585267200,137.0,138.0,135.0,135.6,10.588621,1441.53400749,2020-03-27
163 | 1585353600,138.14,138.14,130.69,131.56,45.832902,6130.87792041,2020-03-28
164 | 1585440000,129.4,131.66,128.14,131.66,9.891126,1285.33523787,2020-03-29
165 | 1585526400,130.13,130.65,125.52,126.28,60.569572,7779.78702877,2020-03-30
166 | 1585612800,124.98,135.6,124.98,132.48,152.470984,20135.61217449,2020-03-31
167 | 1585699200,132.26,133.91,131.55,133.74,57.898012,7674.56069,2020-04-01
168 | 1585785600,132.63,136.09,129.5,135.87,32.640635,4285.96522878,2020-04-02
169 | 1585872000,136.47,149.6,135.63,141.44,22.342406,3203.12816559,2020-04-03
170 | 1585958400,140.64,145.85,139.33,139.97,20.664406,2939.70844103,2020-04-04
171 | 1586044800,139.11,145.46,139.11,143.68,16.166112,2292.17694268,2020-04-05
172 | 1586131200,142.31,143.58,140.87,142.73,16.834544,2404.45603493,2020-04-06
173 | 1586217600,145.83,170.98,145.71,170.98,237.307515,37587.97862357,2020-04-07
174 | 1586304000,172.0,174.99,163.92,164.69,41.338356,7097.58684511,2020-04-08
175 | 1586390400,165.06,171.98,165.06,171.05,48.524669,8218.33769132,2020-04-09
176 | 1586476800,172.22,172.51,165.62,168.35,15.164189,2567.60966724,2020-04-10
177 | 1586563200,160.95,161.53,152.13,157.15,166.996477,25847.72932216,2020-04-11
178 | 1586649600,157.73,160.63,153.65,157.74,36.792405,5781.61530034,2020-04-12
179 | 1586736000,157.65,164.37,154.96,158.18,84.342455,13375.29176522,2020-04-13
180 | 1586822400,157.87,158.09,149.43,156.07,69.3493,10604.97921697,2020-04-14
181 | 1586908800,156.63,161.03,154.92,157.15,41.888893,6621.69104561,2020-04-15
182 | 1586995200,156.25,160.7,152.26,152.26,108.963208,17154.57550787,2020-04-16
183 | 1587081600,149.73,173.9,149.65,171.09,142.268366,23865.27377507,2020-04-17
184 | 1587168000,171.93,173.78,167.96,170.06,150.270344,25615.96924338,2020-04-18
185 | 1587254400,170.54,188.72,170.34,187.12,109.212808,19554.4762698,2020-04-19
186 | 1587340800,186.5,187.54,122.52,179.58,100.225357,18127.69777177,2020-04-20
187 | 1587427200,178.8,185.61,166.07,168.89,117.294276,20405.66096018,2020-04-21
188 | 1587513600,170.49,173.91,168.19,170.34,77.132185,13231.2559939,2020-04-22
189 | 1587600000,170.22,183.45,169.24,182.25,68.941888,12213.48621351,2020-04-23
190 | 1587686400,182.73,193.86,178.55,184.71,147.723165,27512.95165114,2020-04-24
191 | 1587772800,185.49,189.98,185.18,187.54,63.974714,12024.88447868,2020-04-25
192 | 1587859200,186.7,196.81,186.21,193.79,103.081964,19842.06781033,2020-04-26
193 | 1587945600,193.63,199.24,191.96,196.88,68.666885,13395.73327203,2020-04-27
194 | 1588032000,197.94,198.34,190.33,195.99,85.820148,16667.95936644,2020-04-28
195 | 1588118400,195.33,197.03,192.32,196.59,67.585762,13158.97145637,2020-04-29
196 | 1588204800,196.93,218.45,196.08,215.04,91.134091,18852.96781486,2020-04-30
197 |
--------------------------------------------------------------------------------
/crypto-examples/crypto_data/cexio_ltc.csv:
--------------------------------------------------------------------------------
1 | CloseTime,OpenPrice,HighPrice,LowPrice,ClosePrice,Volume,VolumeUSD,DateTime
2 | 1571961600,45.0,50.15,40.0,49.64,9.60593061,478.7449529888,2019-10-25
3 | 1572048000,49.63,58.85,49.63,56.75,442.9393109,24465.355793003,2019-10-26
4 | 1572134400,58.34,60.34,54.15,56.7,1617.8305567,94798.7246878022,2019-10-27
5 | 1572220800,56.68,62.1,55.79,59.83,1121.69908137,65472.6524519846,2019-10-28
6 | 1572307200,60.03,62.34,57.79,57.8,281.92461775,16534.5657635096,2019-10-29
7 | 1572393600,58.01,60.45,57.79,59.93,204.84507263,12103.7600924918,2019-10-30
8 | 1572480000,59.79,60.49,56.87,58.04,282.67233933,16732.433946093,2019-10-31
9 | 1572566400,58.8,59.16,57.02,57.82,78.00903465,4543.327376409,2019-11-01
10 | 1572652800,58.21,58.56,56.6,58.03,139.85066048,8041.1723500953,2019-11-02
11 | 1572739200,58.13,58.88,57.59,58.27,47.84116078,2782.5062407012,2019-11-03
12 | 1572825600,58.2,58.61,57.42,57.86,14.82202332,857.3242702013,2019-11-04
13 | 1572912000,58.15,62.2,58.15,61.41,55.4082075,3366.6487004924,2019-11-05
14 | 1572998400,61.28,64.08,60.63,63.04,76.09016522,4759.4860084132,2019-11-06
15 | 1573084800,62.87,64.08,62.45,63.65,36.63764098,2313.4365864539,2019-11-07
16 | 1573171200,63.89,63.89,60.31,61.45,57.11136492,3545.4448261771,2019-11-08
17 | 1573257600,60.8,62.01,59.37,60.63,108.06193568,6533.8834541704,2019-11-09
18 | 1573344000,60.33,62.24,60.33,62.23,43.9534804,2683.9780009035,2019-11-10
19 | 1573430400,62.03,65.98,61.5,63.6,218.47829666,13786.3747793709,2019-11-11
20 | 1573516800,63.83,64.25,60.7,62.07,53.6814042,3363.1850772496,2019-11-12
21 | 1573603200,61.73,61.92,60.79,61.24,31.4951039,1937.6245403291,2019-11-13
22 | 1573689600,60.99,60.99,60.68,60.68,4.66149231,283.29691599,2019-11-14
23 | 1573776000,60.68,60.68,58.92,59.48,39.86810265,2377.9031511473,2019-11-15
24 | 1573862400,58.9,59.98,56.51,57.37,195.52199577,11182.5778847337,2019-11-16
25 | 1573948800,57.44,58.35,57.28,58.35,17.67394564,1023.4543051659,2019-11-17
26 | 1574035200,57.76,59.91,57.73,59.26,48.04714455,2850.8599815855,2019-11-18
27 | 1574121600,58.63,59.12,54.27,54.36,59.36271042,3351.13969908,2019-11-19
28 | 1574208000,55.65,55.73,54.35,54.73,29.32611549,1612.9771435914,2019-11-20
29 | 1574294400,56.06,56.06,55.06,55.28,28.37153172,1572.1025180078,2019-11-21
30 | 1574380800,54.8,54.8,48.99,50.59,166.41095923,8552.0775530048,2019-11-22
31 | 1574467200,50.96,51.49,43.79,47.04,197.63191839,9299.4381886637,2019-11-23
32 | 1574553600,47.3,48.44,46.69,48.2,205.31249204,9648.8423644669,2019-11-24
33 | 1574640000,47.09,47.44,43.62,43.62,40.98453816,1879.9784683547,2019-11-25
34 | 1574726400,44.92,47.16,42.91,46.12,291.79573391,13190.8572444005,2019-11-26
35 | 1574812800,45.76,47.46,45.76,47.07,99.64838155,4640.0243241695,2019-11-27
36 | 1574899200,46.82,48.64,44.65,47.66,204.54046578,9522.0116101528,2019-11-28
37 | 1574985600,47.74,47.97,46.2,46.2,33.09767167,1569.4627838794,2019-11-29
38 | 1575072000,46.66,49.19,46.66,48.59,63.55421559,3070.4088698603,2019-11-30
39 | 1575158400,48.55,48.56,46.81,47.27,60.07365253,2869.6749555396,2019-12-01
40 | 1575244800,46.71,47.98,45.94,47.76,31.02558848,1455.8918848068,2019-12-02
41 | 1575331200,47.01,47.03,45.22,45.71,63.68784683,2926.0960117351,2019-12-03
42 | 1575417600,45.64,45.84,44.65,45.04,278.40615865,12552.3569968188,2019-12-04
43 | 1575504000,44.75,47.12,43.9,44.68,201.92676488,9245.5829140788,2019-12-05
44 | 1575590400,44.6,44.94,43.86,44.92,81.10669183,3612.5126050339,2019-12-06
45 | 1575676800,44.73,45.31,44.17,45.31,84.87533811,3796.8406354356,2019-12-07
46 | 1575763200,45.34,46.37,45.16,45.16,23.17615984,1056.3116600848,2019-12-08
47 | 1575849600,45.1,45.92,44.98,45.91,24.23125992,1105.5988565154,2019-12-09
48 | 1575936000,45.44,45.58,44.22,44.49,46.29929394,2083.5002777597,2019-12-10
49 | 1576022400,44.5,44.5,43.79,43.97,28.27352874,1247.6670143623,2019-12-11
50 | 1576108800,44.12,44.14,43.49,43.49,20.41492849,895.6717489138,2019-12-12
51 | 1576195200,43.7,43.84,43.18,43.69,22.86729269,996.0844824418,2019-12-13
52 | 1576281600,43.85,44.43,43.72,44.16,16.57574737,730.4537085141,2019-12-14
53 | 1576368000,44.48,44.48,43.44,43.44,11.21528349,491.7058096352,2019-12-15
54 | 1576454400,43.38,43.46,43.38,43.38,9.75,423.215,2019-12-16
55 | 1576540800,42.94,42.95,39.26,39.99,62.44255302,2549.243671502,2019-12-17
56 | 1576627200,39.58,39.75,36.71,36.96,110.96553465,4236.0027361349,2019-12-18
57 | 1576713600,37.1,41.53,36.47,40.88,223.62961133,8729.4778555567,2019-12-19
58 | 1576800000,40.87,41.0,39.41,39.92,65.04223668,2603.1699780284,2019-12-20
59 | 1576886400,39.5,40.26,39.5,40.19,56.89023478,2271.179927939,2019-12-21
60 | 1576972800,40.18,40.18,39.85,39.9,21.16579552,846.8040093444,2019-12-22
61 | 1577059200,40.14,42.0,40.14,41.95,88.95185918,3652.3311803721,2019-12-23
62 | 1577145600,42.32,42.69,40.72,40.72,62.75657094,2629.8713725846,2019-12-24
63 | 1577232000,40.67,41.17,40.1,40.44,55.99421119,2280.7188997942,2019-12-25
64 | 1577318400,40.37,40.39,39.8,40.07,67.06367118,2692.5249654732,2019-12-26
65 | 1577404800,39.76,41.43,39.76,40.22,34.3418338,1388.848417714,2019-12-27
66 | 1577491200,40.41,41.11,39.93,40.91,73.3794798,2979.5287419694,2019-12-28
67 | 1577577600,41.69,43.56,41.67,42.95,54.58951789,2324.4585725075,2019-12-29
68 | 1577664000,42.3,43.63,42.29,43.26,80.72968047,3478.3209779704,2019-12-30
69 | 1577750400,43.04,44.0,42.16,42.47,83.04743789,3561.6375341254,2019-12-31
70 | 1577836800,42.49,42.76,41.23,41.23,38.13490482,1608.4001795455,2020-01-01
71 | 1577923200,41.79,41.94,41.65,41.65,19.18958538,801.5935444888,2020-01-02
72 | 1578009600,41.38,41.53,39.54,39.54,93.18612749,3792.3588746186,2020-01-03
73 | 1578096000,39.4,42.24,38.96,42.21,143.13443881,5796.1502876121,2020-01-04
74 | 1578182400,42.21,42.76,41.94,42.62,26.39353589,1119.4982470029,2020-01-05
75 | 1578268800,43.0,44.28,42.8,43.09,74.0070653,3217.3967301639,2020-01-06
76 | 1578355200,44.03,46.04,44.03,45.8,118.92835903,5347.4787832127,2020-01-07
77 | 1578441600,46.06,46.7,44.66,46.6,255.96376867,11781.0818669183,2020-01-08
78 | 1578528000,46.8,48.06,44.37,44.81,93.80936118,4344.346130463,2020-01-09
79 | 1578614400,45.19,45.28,44.08,44.53,68.28409113,3054.8409964617,2020-01-10
80 | 1578700800,44.83,49.23,44.04,48.49,205.35590405,9719.8495272089,2020-01-11
81 | 1578787200,48.74,51.65,48.13,49.61,251.32212381,12504.8022079537,2020-01-12
82 | 1578873600,48.97,51.52,48.94,51.25,101.07538718,5115.171244513,2020-01-13
83 | 1578960000,51.24,51.24,48.97,49.87,143.25330405,7174.5013228378,2020-01-14
84 | 1579046400,49.72,60.69,49.72,58.92,701.69471705,39652.3047455727,2020-01-15
85 | 1579132800,58.62,60.26,55.52,57.99,388.59692856,22633.0643163998,2020-01-16
86 | 1579219200,58.24,58.24,55.15,57.99,210.14123478,11907.640214435,2020-01-17
87 | 1579305600,57.68,62.94,56.82,61.75,285.97365374,17265.4792951855,2020-01-18
88 | 1579392000,61.21,62.35,58.63,59.3,233.05848616,13991.9870624301,2020-01-19
89 | 1579478400,59.3,62.46,55.51,57.31,199.42810222,11714.341387564,2020-01-20
90 | 1579564800,57.52,57.83,56.09,57.38,88.3173296,5044.8433852118,2020-01-21
91 | 1579651200,57.59,57.77,56.86,57.48,47.73967798,2737.1260926408,2020-01-22
92 | 1579737600,58.88,58.88,57.71,58.21,38.20843171,2229.2578263576,2020-01-23
93 | 1579824000,57.34,57.34,52.71,53.9,170.18475165,9328.7447614684,2020-01-24
94 | 1579910400,53.41,55.32,50.81,55.04,94.29020673,5046.1907386595,2020-01-25
95 | 1579996800,53.01,53.94,52.41,53.94,65.25705145,3466.8780214467,2020-01-26
96 | 1580083200,53.1,56.38,53.08,56.05,72.34695071,3947.8755631714,2020-01-27
97 | 1580169600,56.81,59.39,56.79,59.12,71.76004369,4171.8203339317,2020-01-28
98 | 1580256000,59.26,61.16,58.15,60.73,183.28399552,10939.3104438641,2020-01-29
99 | 1580342400,60.7,62.33,59.76,60.02,170.7047845,10393.4601028999,2020-01-30
100 | 1580428800,59.02,69.81,58.94,68.13,302.51884611,19920.0941874264,2020-01-31
101 | 1580515200,68.34,68.89,66.05,68.0,220.60393363,14914.5287622763,2020-02-01
102 | 1580601600,68.46,72.81,68.46,70.8,241.57204518,17104.8484893567,2020-02-02
103 | 1580688000,70.69,73.0,68.07,70.16,165.20734399,11651.5941839068,2020-02-03
104 | 1580774400,69.58,72.68,68.43,69.64,134.56948231,9459.2636392813,2020-02-04
105 | 1580860800,70.04,70.21,66.88,67.42,103.43598856,7095.2829407356,2020-02-05
106 | 1580947200,68.38,73.0,68.19,72.59,145.92483818,10457.028132411,2020-02-06
107 | 1581033600,73.78,74.97,71.81,73.07,139.0642035,10214.8058577136,2020-02-07
108 | 1581120000,73.58,75.21,73.19,73.63,140.91537608,10413.7615514842,2020-02-08
109 | 1581206400,74.42,77.49,71.76,76.59,109.007677,8147.7082720866,2020-02-09
110 | 1581292800,76.68,78.03,75.75,77.0,68.54185955,5284.0795337205,2020-02-10
111 | 1581379200,77.13,77.13,72.53,74.18,236.6400034,17605.5480176149,2020-02-11
112 | 1581465600,73.51,77.0,72.77,76.28,125.57299701,9405.2636574577,2020-02-12
113 | 1581552000,77.0,82.22,76.54,80.98,110.64075849,8778.3925737853,2020-02-13
114 | 1581638400,81.49,83.17,77.32,80.28,253.81847989,20520.7050708591,2020-02-14
115 | 1581724800,80.55,83.16,78.62,82.98,119.76686859,9732.0562704039,2020-02-15
116 | 1581811200,81.39,82.0,75.32,76.2,126.43405553,9931.7463933948,2020-02-16
117 | 1581897600,76.54,80.44,68.02,74.93,258.22659813,19289.5122358916,2020-02-17
118 | 1581984000,74.63,74.63,68.75,72.91,258.13577866,18424.9509992375,2020-02-18
119 | 1582070400,74.07,78.35,70.07,76.96,168.07055625,12502.6228167391,2020-02-19
120 | 1582156800,76.3,77.63,67.75,71.23,235.70624513,17348.5425425936,2020-02-20
121 | 1582243200,70.6,72.09,66.18,69.47,204.09481205,14132.970347151,2020-02-21
122 | 1582329600,69.35,74.63,69.03,73.17,215.88321111,15650.1266303257,2020-02-22
123 | 1582416000,73.66,76.36,72.41,74.56,141.857,10589.96185,2020-02-23
124 | 1582502400,76.23,79.76,76.23,79.76,63.98099999,5014.9550392168,2020-02-24
125 | 1582588800,79.35,79.38,71.87,75.73,303.73211845,22930.7019333122,2020-02-25
126 | 1582675200,75.66,75.66,69.89,71.37,141.6158837,10365.1938209426,2020-02-26
127 | 1582761600,67.33,69.05,60.44,60.85,133.41959741,8625.5593759459,2020-02-27
128 | 1582848000,58.91,64.76,58.8,62.03,51.7397821,3167.108631688,2020-02-28
129 | 1582934400,60.69,60.69,57.18,58.33,52.08302499,3047.2918550569,2020-02-29
130 | 1583020800,59.25,59.25,58.5,59.1,14.41724327,849.58872948,2020-03-01
131 | 1583107200,58.75,59.61,56.67,56.67,18.67870679,1082.9791442769,2020-03-02
132 | 1583193600,59.9,61.92,59.48,61.92,12.35003031,746.409403496,2020-03-03
133 | 1583280000,60.55,60.99,60.03,60.99,19.28359265,1164.5363157235,2020-03-04
134 | 1583366400,61.63,61.63,58.96,60.45,72.4390444,4358.2326723796,2020-03-05
135 | 1583452800,60.56,62.79,60.56,61.79,7.54754858,467.2717346487,2020-03-06
136 | 1583539200,65.87,65.87,62.59,62.59,13.96652129,890.080806045,2020-03-07
137 | 1583625600,62.92,63.66,60.73,60.73,26.31356766,1625.1869013274,2020-03-08
138 | 1583712000,57.81,57.81,51.61,52.73,120.94335811,6726.7145678666,2020-03-09
139 | 1583798400,51.92,52.08,46.94,50.18,102.14682417,5029.9039955262,2020-03-10
140 | 1583884800,50.77,51.1,49.95,50.49,35.96867385,1812.5875865552,2020-03-11
141 | 1583971200,49.37,49.37,46.11,48.97,71.59233146,3454.5382481697,2020-03-12
142 | 1584057600,48.21,48.21,30.66,32.01,330.29168111,13097.9319532789,2020-03-13
143 | 1584144000,27.82,37.97,25.68,37.86,436.87538078,15299.6298293077,2020-03-14
144 | 1584230400,36.78,36.78,26.42,35.11,18.38234153,649.0439148881,2020-03-15
145 | 1584316800,35.5,36.23,35.5,35.86,11.63746141,417.7621789206,2020-03-16
146 | 1584403200,31.42,31.42,30.03,30.19,24.039336,726.785682795,2020-03-17
147 | 1584489600,35.98,36.17,34.44,36.17,5.21392006,184.3946308826,2020-03-18
148 | 1584576000,33.95,34.28,32.97,33.96,19.01676849,640.2260647939,2020-03-19
149 | 1584662400,35.06,40.44,34.74,39.45,95.84755199,3604.6271690656,2020-03-20
150 | 1584748800,38.84,43.33,34.43,37.16,257.1409226,10359.5212008361,2020-03-21
151 | 1584835200,37.63,39.73,36.87,38.86,92.20590428,3510.5758925418,2020-03-22
152 | 1584921600,39.65,39.86,35.19,35.19,110.00710163,4082.6490005997,2020-03-23
153 | 1585008000,34.79,39.89,34.79,38.79,141.40122858,5344.2898602443,2020-03-24
154 | 1585094400,39.25,40.91,38.25,40.71,194.30679942,7666.7832724194,2020-03-25
155 | 1585180800,40.42,40.89,38.57,39.38,66.51816055,2643.3346887074,2020-03-26
156 | 1585267200,39.36,39.53,38.69,39.53,11.74476003,458.7074402341,2020-03-27
157 | 1585353600,40.78,40.91,37.78,38.5,54.94466481,2164.7954871414,2020-03-28
158 | 1585440000,37.79,39.03,36.99,38.77,113.07273034,4295.0629758266,2020-03-29
159 | 1585526400,38.12,38.91,36.97,37.23,58.77955834,2249.9455251059,2020-03-30
160 | 1585612800,37.13,39.27,37.13,38.96,32.66073931,1261.2670711353,2020-03-31
161 | 1585699200,38.89,39.18,38.62,39.18,15.70544139,611.6801400715,2020-04-01
162 | 1585785600,38.6,38.97,37.52,38.84,17.82210657,679.9953397152,2020-04-02
163 | 1585872000,39.44,41.91,39.44,39.92,59.93518958,2406.4951438643,2020-04-03
164 | 1585958400,39.71,40.96,39.71,40.18,41.87200356,1688.7624354398,2020-04-04
165 | 1586044800,40.61,40.97,40.27,40.55,20.53950408,832.3178034564,2020-04-05
166 | 1586131200,40.71,40.71,40.14,40.61,9.94999999,402.3054995986,2020-04-06
167 | 1586217600,41.04,44.5,41.04,44.31,66.92496588,2932.0333563522,2020-04-07
168 | 1586304000,45.62,46.1,45.62,45.62,13.20308941,603.9569388746,2020-04-08
169 | 1586390400,44.52,46.53,44.52,45.97,5.52786784,253.6091634973,2020-04-09
170 | 1586476800,45.47,46.8,45.29,46.62,12.65820934,580.4453786898,2020-04-10
171 | 1586563200,46.64,46.64,40.92,42.23,329.79516993,13890.3611058912,2020-04-11
172 | 1586649600,42.33,43.07,41.4,42.44,354.93755019,14991.5709257821,2020-04-12
173 | 1586736000,42.56,43.48,41.76,41.9,345.50866145,14660.1258979181,2020-04-13
174 | 1586822400,41.87,41.87,39.5,41.06,541.87488843,21978.8515536302,2020-04-14
175 | 1586908800,41.19,41.78,40.46,41.18,243.44154677,10031.2269601892,2020-04-15
176 | 1586995200,41.08,41.71,39.07,39.2,227.99918866,9242.8219253269,2020-04-16
177 | 1587081600,39.18,43.55,38.27,42.486,464.35352004,19656.94085647825,2020-04-17
178 | 1587168000,42.605,42.845,41.515,42.316,156.80511795,6626.68615627199,2020-04-18
179 | 1587254400,42.295,44.186,41.995,43.925,201.51286426,8667.26580089239,2020-04-19
180 | 1587340800,44.02,44.02,36.657,42.23,195.58569736,8380.35541649433,2020-04-20
181 | 1587427200,42.495,43.205,39.835,40.326,166.67020826,6994.57929136807,2020-04-21
182 | 1587513600,40.294,41.25,40.135,40.645,82.3575586,3347.98668739749,2020-04-22
183 | 1587600000,40.445,42.165,40.275,41.825,115.02905084,4755.9945055067,2020-04-23
184 | 1587686400,41.815,43.878,41.125,42.855,308.81681677,13190.49436445234,2020-04-24
185 | 1587772800,43.07,44.955,42.845,44.333,249.8813921,10979.68172512306,2020-04-25
186 | 1587859200,44.138,45.362,43.842,44.415,332.96205552,14911.63595295652,2020-04-26
187 | 1587945600,44.36,45.355,43.267,44.695,197.03071686,8810.39532670776,2020-04-27
188 | 1588032000,44.715,45.185,43.475,44.396,235.12350302,10405.7598652706,2020-04-28
189 | 1588118400,44.575,45.856,43.445,45.705,270.13589807,12011.11142850369,2020-04-29
190 | 1588204800,45.915,49.905,45.67,48.879,212.62438767,10143.0061309097,2020-04-30
191 |
--------------------------------------------------------------------------------
/crypto-examples/crypto_data/kraken_btc.csv:
--------------------------------------------------------------------------------
1 | CloseTime,OpenPrice,HighPrice,LowPrice,ClosePrice,Volume,VolumeUSD,DateTime
2 | 1576800000,7140.0,7140.0,7140.0,7140.0,0.00429554,30.6701556,2019-12-20
3 | 1576886400,7112.8,7832.0,7112.8,7186.9,1.15254726,8336.473862649,2019-12-21
4 | 1576972800,7183.0,7183.0,7050.0,7132.1,6.58549942,46773.123642833,2019-12-22
5 | 1577059200,7123.7,7489.5,7123.7,7489.5,5.34243747,38759.341159868,2019-12-23
6 | 1577145600,7526.4,7667.5,6666.1,7325.6,14.02647699,104173.814827871,2019-12-24
7 | 1577232000,7308.1,7579.9,7150.0,7235.2,7.78091337,56809.238327718,2019-12-25
8 | 1577318400,7240.0,7349.9,7004.7,7188.9,6.72374747,48472.409338152,2019-12-26
9 | 1577404800,7199.5,7579.9,6075.3,7229.6,16.87632604,121706.330183964,2019-12-27
10 | 1577491200,7209.1,7263.3,7121.6,7263.3,10.98163089,78999.162000486,2019-12-28
11 | 1577577600,7314.0,7700.8,7278.4,7314.6,4.73741946,34899.243560348,2019-12-29
12 | 1577664000,7314.6,7536.0,7120.0,7403.1,11.84539088,87255.266652153,2019-12-30
13 | 1577750400,7379.0,7449.9,7208.1,7236.0,9.58188592,69839.446407669,2019-12-31
14 | 1577836800,7249.4,7299.8,7133.0,7180.9,22.10114343,160045.233666738,2020-01-01
15 | 1577923200,7181.0,7331.5,7180.9,7229.0,3.05347529,22127.675415561,2020-01-02
16 | 1578009600,7201.2,7279.2,6941.9,6972.1,9.0644629,63991.395907252,2020-01-03
17 | 1578096000,6942.4,7400.0,6942.4,7316.4,19.72361032,140850.611266635,2020-01-04
18 | 1578182400,7345.2,7372.7,7301.5,7346.4,2.62569662,19243.050833236,2020-01-05
19 | 1578268800,7355.9,7495.0,7323.7,7323.7,16.28533723,121740.022592685,2020-01-06
20 | 1578355200,7362.8,7746.3,7362.8,7746.3,12.45713964,93935.250400813,2020-01-07
21 | 1578441600,7835.0,8200.0,7781.4,8151.5,22.74769758,181423.146128179,2020-01-08
22 | 1578528000,8200.0,8445.0,7385.0,8117.0,15.01313721,123299.56744578,2020-01-09
23 | 1578614400,8053.9,8121.9,7700.2,7828.7,17.75797912,140331.969058345,2020-01-10
24 | 1578700800,7828.7,8200.9,7695.5,8086.5,8.38490807,66305.56721708,2020-01-11
25 | 1578787200,8173.3,8251.0,7489.8,8073.7,10.60291272,85666.348095299,2020-01-12
26 | 1578873600,7956.3,8170.6,7950.2,8117.1,3.78966131,30770.273520923,2020-01-13
27 | 1578960000,8173.2,8185.8,8021.5,8115.0,3.50197747,28375.240026383,2020-01-14
28 | 1579046400,8194.1,8876.5,8194.1,8843.4,20.97728226,180543.944639807,2020-01-15
29 | 1579132800,8756.1,8900.0,8628.3,8850.0,14.68875729,128713.526467206,2020-01-16
30 | 1579219200,8751.2,8770.0,8566.7,8708.7,17.31935036,150749.457264484,2020-01-17
31 | 1579305600,8712.9,9010.0,8712.9,8908.7,13.86288418,123649.818952328,2020-01-18
32 | 1579392000,8888.6,8961.5,8835.0,8961.5,7.22768468,64351.730907681,2020-01-19
33 | 1579478400,8944.2,9181.7,8051.0,8648.9,29.08199399,251896.170920761,2020-01-20
34 | 1579564800,8721.1,8721.1,8558.8,8699.6,25.36879398,219953.276274673,2020-01-21
35 | 1579651200,8637.7,8779.4,8536.2,8757.5,11.21671573,97421.522795567,2020-01-22
36 | 1579737600,8718.1,8854.9,8611.2,8675.3,10.80689178,94439.113864625,2020-01-23
37 | 1579824000,8590.0,8604.5,8292.1,8417.2,11.5774717,97423.717640981,2020-01-24
38 | 1579910400,8386.8,8516.5,8261.0,8444.0,9.9182936,83331.650276095,2020-01-25
39 | 1579996800,8440.1,8440.1,8251.0,8362.8,11.56733721,96224.000569497,2020-01-26
40 | 1580083200,8334.0,8613.3,8334.0,8593.1,3.86258367,32851.779954252,2020-01-27
41 | 1580169600,8619.4,8980.5,8578.1,8901.0,24.229844,211876.264507609,2020-01-28
42 | 1580256000,8970.7,9400.0,8825.1,9372.7,11.08945562,100865.876287019,2020-01-29
43 | 1580342400,9388.9,9445.2,9144.5,9301.0,9.48734359,88362.472500377,2020-01-30
44 | 1580428800,9301.0,9580.0,9158.7,9569.8,14.26921207,133671.050379398,2020-01-31
45 | 1580515200,9511.5,9511.5,9239.1,9353.2,11.76387971,110364.327967419,2020-02-01
46 | 1580601600,9408.6,9425.0,9324.3,9380.8,4.46454672,41885.926094292,2020-02-02
47 | 1580688000,9353.0,9481.7,9175.0,9355.3,15.79954493,148558.369826075,2020-02-03
48 | 1580774400,9325.0,9600.0,9200.0,9307.7,30.43082122,283754.57863445,2020-02-04
49 | 1580860800,9311.0,9600.0,9101.0,9210.4,28.24017655,262382.941781395,2020-02-05
50 | 1580947200,9215.0,9716.8,9188.0,9648.8,8.51885739,80186.473124174,2020-02-06
51 | 1581033600,9560.8,9850.0,9542.8,9755.9,24.86164246,243108.314995056,2020-02-07
52 | 1581120000,9817.0,9892.0,9746.5,9768.0,19.1481345,187791.76244882,2020-02-08
53 | 1581206400,9786.2,9920.0,9684.7,9897.7,6.77698222,66435.484453687,2020-02-09
54 | 1581292800,9913.2,10159.9,9806.3,10126.7,10.57796433,106229.904429435,2020-02-10
55 | 1581379200,10185.3,10200.4,9650.0,9877.1,10.6440338,105120.218389342,2020-02-11
56 | 1581465600,9819.9,10317.3,9732.7,10207.1,19.39839311,195714.878157124,2020-02-12
57 | 1581552000,10249.0,10425.0,9551.1,10336.1,28.48924504,292431.183934722,2020-02-13
58 | 1581638400,10357.2,10500.0,9551.0,10230.0,52.55372297,535382.455055523,2020-02-14
59 | 1581724800,10216.5,10500.0,9451.0,10320.3,41.84471923,426339.38240192,2020-02-15
60 | 1581811200,10362.4,10500.0,9659.6,9899.1,33.96922434,341392.80753909,2020-02-16
61 | 1581897600,9894.6,10088.4,9631.5,9950.1,19.37777455,191031.867517167,2020-02-17
62 | 1581984000,9925.1,9933.0,9460.0,9684.6,10.84734391,104932.610005753,2020-02-18
63 | 1582070400,9694.8,10438.5,9603.1,10149.5,22.83685662,226646.793958732,2020-02-19
64 | 1582156800,10120.2,10217.3,9385.1,9589.5,45.92579105,459632.888513495,2020-02-20
65 | 1582243200,9611.4,9655.7,9420.0,9615.9,37.5564152,358654.837768166,2020-02-21
66 | 1582329600,9601.5,9743.9,8560.0,9670.9,20.57552681,195220.213729304,2020-02-22
67 | 1582416000,9694.8,9694.8,9494.2,9658.8,9.30354999,89705.186047917,2020-02-23
68 | 1582502400,9800.0,9970.5,9800.0,9950.0,8.9114606,88057.484191752,2020-02-24
69 | 1582588800,9947.0,9980.0,9483.0,9649.7,30.00250046,290624.064525312,2020-02-25
70 | 1582675200,9624.0,9636.0,9257.7,9318.6,30.8970806,290692.087806848,2020-02-26
71 | 1582761600,9304.3,9512.4,8508.0,8828.8,58.90910999,525113.308822201,2020-02-27
72 | 1582848000,8778.0,9293.1,8500.4,8782.6,66.52303279,584625.095374711,2020-02-28
73 | 1582934400,8812.8,8888.2,7211.0,8700.0,59.04220577,508336.564883513,2020-02-29
74 | 1583020800,8734.1,8765.1,8536.1,8551.5,10.42195251,90076.46567861,2020-03-01
75 | 1583107200,8539.2,8719.0,8415.3,8540.3,11.79477687,100604.48623903,2020-03-02
76 | 1583193600,8501.8,8954.6,8433.8,8889.9,37.23827378,328446.340447403,2020-03-03
77 | 1583280000,8859.7,8882.7,8652.6,8758.2,8.99393476,78617.010082579,2020-03-04
78 | 1583366400,8774.0,8833.0,8333.3,8750.2,76.32559703,663541.724159745,2020-03-05
79 | 1583452800,8769.7,9204.8,8465.0,9024.5,49.81799744,448753.470614239,2020-03-06
80 | 1583539200,9050.5,9700.0,8637.0,9138.0,67.21121729,616320.813096818,2020-03-07
81 | 1583625600,9138.0,9170.0,8859.3,8888.0,26.87715505,244017.050203799,2020-03-08
82 | 1583712000,8854.6,8854.6,7850.0,8025.0,39.62793601,332554.725509182,2020-03-09
83 | 1583798400,8034.0,8455.0,7550.1,7925.2,53.69906882,423632.792645869,2020-03-10
84 | 1583884800,7924.2,8154.3,7723.1,7911.3,42.41255066,336665.580490474,2020-03-11
85 | 1583971200,7903.4,7950.0,7600.0,7945.6,32.94608593,256706.568795227,2020-03-12
86 | 1584057600,7843.5,7843.5,4359.5,4851.0,449.53667571,2794348.823436017,2020-03-13
87 | 1584144000,4823.0,6240.0,3883.8,5553.6,988.79391827,5096018.76160014,2020-03-14
88 | 1584230400,5458.2,5657.5,4650.0,5066.5,121.8060737,635367.800947143,2020-03-15
89 | 1584316800,5110.0,5962.4,4327.0,5388.9,496.49849062,2509976.314781294,2020-03-16
90 | 1584403200,5340.2,5349.9,4418.0,4964.6,170.62446542,818904.767110642,2020-03-17
91 | 1584489600,5026.7,5520.1,4970.3,5336.7,96.63367605,511046.954057968,2020-03-18
92 | 1584576000,5336.7,5410.7,5011.2,5374.7,109.61050564,570503.437949644,2020-03-19
93 | 1584662400,5410.9,6385.2,5295.1,6212.0,360.71536298,2066662.023346035,2020-03-20
94 | 1584748800,6219.4,6900.0,5646.3,6127.8,153.34207965,961544.258257471,2020-03-21
95 | 1584835200,6188.0,6470.5,5911.2,6152.1,47.59090241,292757.533842853,2020-03-22
96 | 1584921600,6166.9,6385.7,5746.5,5840.4,38.82389068,232095.335127662,2020-03-23
97 | 1585008000,5773.8,6732.0,5710.0,6486.6,89.32622586,560266.163518091,2020-03-24
98 | 1585094400,6522.8,6795.4,6394.2,6747.3,137.99505627,913338.165589722,2020-03-25
99 | 1585180800,6645.3,6943.0,6451.7,6660.1,59.71825276,396834.648501291,2020-03-26
100 | 1585267200,6669.7,6754.5,6500.2,6750.0,63.25911271,420921.639283517,2020-03-27
101 | 1585353600,6774.2,6847.1,6262.3,6370.0,53.76343833,355653.79557356,2020-03-28
102 | 1585440000,6370.0,6370.0,6050.0,6224.1,68.61560694,425416.436654241,2020-03-29
103 | 1585526400,6221.5,6221.5,5868.9,5868.9,40.53056558,244286.172170935,2020-03-30
104 | 1585612800,5861.0,6580.0,5861.0,6420.0,34.52397724,217518.219861739,2020-03-31
105 | 1585699200,6452.8,6503.0,6324.6,6401.9,39.12713397,251460.25136307,2020-04-01
106 | 1585785600,6353.0,6680.0,6157.0,6624.8,62.96916494,398071.142797805,2020-04-02
107 | 1585872000,6664.5,7200.0,6566.9,6776.5,110.32802921,754636.849713364,2020-04-03
108 | 1585958400,6785.6,7042.1,6607.9,6766.9,30.50242693,208023.551038331,2020-04-04
109 | 1586044800,6721.4,6967.1,6670.3,6871.6,27.86905677,189205.170695237,2020-04-05
110 | 1586131200,6880.6,6886.4,6671.6,6774.7,36.15298132,244991.664829336,2020-04-06
111 | 1586217600,6805.1,7343.8,6000.0,7339.8,141.73928309,1004849.803566865,2020-04-07
112 | 1586304000,7339.2,7449.5,7078.0,7195.6,99.41108186,723398.828205389,2020-04-08
113 | 1586390400,7195.1,8750.0,7166.0,7369.7,88.80446233,657454.827816836,2020-04-09
114 | 1586476800,7340.2,7413.6,7122.8,7317.7,31.25265174,228399.385218163,2020-04-10
115 | 1586563200,7299.0,7299.0,6728.2,6859.6,69.90471782,483936.855706672,2020-04-11
116 | 1586649600,6875.9,6940.3,6762.4,6856.2,47.05698554,322042.803832029,2020-04-12
117 | 1586736000,6898.0,7180.0,6785.0,6900.0,79.73514993,554925.819235402,2020-04-13
118 | 1586822400,6857.9,7200.0,5500.0,6823.0,125.16735257,823584.132294981,2020-04-14
119 | 1586908800,6851.5,6964.4,6771.7,6860.2,41.63762288,285956.195103652,2020-04-15
120 | 1586995200,6818.2,6908.6,6610.0,6627.3,44.12187619,297074.953661171,2020-04-16
121 | 1587081600,6620.8,7182.7,6488.4,7087.3,58.42971725,405416.976450597,2020-04-17
122 | 1587168000,7072.5,7145.3,6992.1,7051.8,58.37535223,413040.65414415,2020-04-18
123 | 1587254400,7042.5,7271.9,7040.2,7243.2,121.83747442,876803.956996556,2020-04-19
124 | 1587340800,7225.2,7237.1,7025.3,7126.4,37.13532215,264381.525601432,2020-04-20
125 | 1587427200,7130.0,7201.4,6766.0,6848.6,122.39594572,852861.154457393,2020-04-21
126 | 1587513600,6835.0,6929.2,6772.9,6848.4,46.86129789,321121.351765615,2020-04-22
127 | 1587600000,6840.0,7153.8,6820.9,7110.3,70.57644055,494095.472041567,2020-04-23
128 | 1587686400,7130.0,7738.6,7028.5,7450.0,120.91010564,895210.05199225,2020-04-24
129 | 1587772800,7510.0,7606.2,7360.6,7491.4,120.45958553,904094.387113953,2020-04-25
130 | 1587859200,7476.0,7688.0,7461.0,7541.3,49.50024032,374553.233524353,2020-04-26
131 | 1587945600,7545.0,7701.0,7494.7,7685.0,54.78964094,416511.170796101,2020-04-27
132 | 1588032000,7692.9,7777.0,7625.6,7770.0,71.53824959,551807.600359404,2020-04-28
133 | 1588118400,7717.6,7758.4,7666.2,7745.9,47.69622608,368912.038228526,2020-04-29
134 | 1588204800,7750.1,8748.5,7733.0,8636.6,194.92434354,1593852.341089531,2020-04-30
135 |
--------------------------------------------------------------------------------
/crypto-examples/crypto_data/kraken_eth.csv:
--------------------------------------------------------------------------------
1 | CloseTime,OpenPrice,HighPrice,LowPrice,ClosePrice,Volume,VolumeUSD,DateTime
2 | 1576800000,140.0,140.0,126.6,128.2,2.69,352.75,2019-12-20
3 | 1576886400,128.3,129.0,126.0,128.22,72.53296405,9285.1477061641,2019-12-21
4 | 1576972800,127.9,127.9,126.91,126.91,42.38773055,5403.9561648879,2019-12-22
5 | 1577059200,126.51,133.23,126.51,131.99,60.5579744,7847.3763311461,2019-12-23
6 | 1577145600,132.58,134.06,126.87,127.75,144.05646243,18954.0727984955,2019-12-24
7 | 1577232000,127.65,128.97,126.73,127.91,129.01761342,16461.5473745506,2019-12-25
8 | 1577318400,126.87,126.87,123.86,125.49,107.4484065,13443.2656806072,2019-12-26
9 | 1577404800,125.1,133.43,124.54,124.54,95.54374803,12119.5434370775,2019-12-27
10 | 1577491200,125.44,126.23,123.47,126.18,311.01203048,38800.8597781501,2019-12-28
11 | 1577577600,127.06,129.65,127.06,129.0,48.14439797,6151.9400479859,2019-12-29
12 | 1577664000,128.0,137.78,128.0,135.28,176.69656164,23873.0074908285,2019-12-30
13 | 1577750400,133.63,134.62,130.28,132.11,350.28872276,45984.3281962165,2019-12-31
14 | 1577836800,131.68,137.66,128.66,128.66,122.51832095,16151.8223406534,2020-01-01
15 | 1577923200,130.28,131.15,130.28,130.59,5.75597655,751.3167491291,2020-01-02
16 | 1578009600,130.5,130.5,126.69,127.24,311.57704113,39646.0644101201,2020-01-03
17 | 1578096000,126.38,134.4,126.38,133.86,138.71528783,18290.5992317127,2020-01-04
18 | 1578182400,134.12,135.24,132.95,134.5,44.50658291,5987.4416090788,2020-01-05
19 | 1578268800,136.16,137.91,116.77,135.24,404.83771074,54589.0484540781,2020-01-06
20 | 1578355200,135.02,144.39,135.02,144.39,401.71016842,56646.2050641939,2020-01-07
21 | 1578441600,144.86,144.87,139.17,143.39,418.76485744,59254.3672795955,2020-01-08
22 | 1578528000,144.33,147.0,135.01,135.02,186.10670571,26787.4760806894,2020-01-09
23 | 1578614400,141.42,141.42,136.1,137.52,63.13832739,8676.2700350808,2020-01-10
24 | 1578700800,138.04,144.58,135.57,143.84,150.15715902,20973.7799256099,2020-01-11
25 | 1578787200,144.41,146.74,142.42,143.01,49.7168133,7148.5797870357,2020-01-12
26 | 1578873600,142.29,145.67,142.29,144.63,57.89505613,8338.6441024425,2020-01-13
27 | 1578960000,146.21,146.21,141.94,141.94,56.16731417,8083.6724305276,2020-01-14
28 | 1579046400,144.54,169.19,135.0,166.22,367.4292913,58610.4289417611,2020-01-15
29 | 1579132800,164.0,171.5,161.11,165.41,236.15831582,38996.3160127907,2020-01-16
30 | 1579219200,164.79,164.79,155.65,164.35,85.5934374,13805.4998809834,2020-01-17
31 | 1579305600,162.86,174.02,162.86,171.57,167.47847979,28564.8231981232,2020-01-18
32 | 1579392000,170.79,179.62,145.09,175.61,419.48570448,72686.8958744428,2020-01-19
33 | 1579478400,173.97,177.39,163.11,167.11,156.23558315,26717.391223579,2020-01-20
34 | 1579564800,165.12,169.03,163.02,167.32,166.00868859,27560.809481304,2020-01-21
35 | 1579651200,167.0,169.73,165.06,169.73,141.81194552,23776.5224618682,2020-01-22
36 | 1579737600,168.82,171.25,164.62,167.95,55.58974949,9369.3606911416,2020-01-23
37 | 1579824000,167.92,167.92,159.71,162.68,265.00720624,43079.6415970571,2020-01-24
38 | 1579910400,160.73,163.79,155.71,163.79,99.69643374,15882.5717554793,2020-01-25
39 | 1579996800,159.61,162.0,158.49,162.0,22.14992468,3547.9977639797,2020-01-26
40 | 1580083200,160.53,167.69,160.52,167.6,334.59220367,55375.2926569619,2020-01-27
41 | 1580169600,169.2,171.88,167.02,171.33,24.70318965,4184.1902370336,2020-01-28
42 | 1580256000,173.5,176.0,169.32,176.0,295.19876429,50973.7958425193,2020-01-29
43 | 1580342400,176.7,180.99,175.0,175.0,330.99786684,58574.0193024721,2020-01-30
44 | 1580428800,173.46,184.8,171.2,184.38,154.68952646,27443.6067279105,2020-01-31
45 | 1580515200,185.04,187.06,175.51,180.0,367.97958165,66671.3012136244,2020-02-01
46 | 1580601600,182.51,183.94,181.14,183.1,53.60566255,9740.3499005624,2020-02-02
47 | 1580688000,181.72,192.61,180.01,188.89,164.04170909,30878.2699620848,2020-02-03
48 | 1580774400,188.16,193.98,185.04,188.99,289.64719827,54834.7945898788,2020-02-04
49 | 1580860800,190.94,190.94,185.01,188.43,99.31930458,18613.4758736629,2020-02-05
50 | 1580947200,189.45,204.8,189.45,204.18,255.76714362,50569.3735906971,2020-02-06
51 | 1581033600,203.7,216.51,202.09,213.4,357.78064328,75944.3876470475,2020-02-07
52 | 1581120000,215.9,225.0,215.52,223.51,490.20173287,107799.1929707245,2020-02-08
53 | 1581206400,223.57,228.87,195.11,224.33,411.58010656,89857.8656430362,2020-02-09
54 | 1581292800,223.91,229.21,220.16,226.59,438.56184482,99193.1029156814,2020-02-10
55 | 1581379200,229.0,229.0,216.61,224.58,293.34285745,64741.6170674169,2020-02-11
56 | 1581465600,220.54,238.36,218.5,236.4,481.28310011,109397.858107986,2020-02-12
57 | 1581552000,240.31,276.57,240.31,265.97,644.9233929,164197.3397469339,2020-02-13
58 | 1581638400,265.79,276.71,237.45,267.89,799.54649405,213051.9380411429,2020-02-14
59 | 1581724800,268.13,286.29,261.55,286.29,570.30285559,157367.5891831152,2020-02-15
60 | 1581811200,287.93,288.0,252.55,263.77,1064.23245075,291360.0168279053,2020-02-16
61 | 1581897600,264.95,272.81,237.45,259.42,642.13059024,163488.2768706213,2020-02-17
62 | 1581984000,259.42,267.79,243.35,267.55,414.21799304,105738.1597765169,2020-02-18
63 | 1582070400,266.68,286.32,260.0,281.57,790.04303889,218965.2921161444,2020-02-19
64 | 1582156800,281.47,286.05,252.75,259.19,749.02004389,204726.2391529845,2020-02-20
65 | 1582243200,257.34,263.25,246.4,258.34,507.76126943,129775.6178790002,2020-02-21
66 | 1582329600,255.74,270.45,254.64,262.95,391.97913881,103769.9100440502,2020-02-22
67 | 1582416000,265.63,265.64,256.85,263.36,419.10473181,110010.7727006392,2020-02-23
68 | 1582502400,262.51,275.68,261.74,275.68,149.0697582,40439.6162486048,2020-02-24
69 | 1582588800,275.38,275.38,257.44,265.84,552.59555331,143815.7112222941,2020-02-25
70 | 1582675200,264.57,311.43,245.0,247.1,761.9262399,194521.0214297529,2020-02-26
71 | 1582761600,246.02,286.57,209.4,224.43,3081.88927703,712023.5545236794,2020-02-27
72 | 1582848000,223.64,238.3,210.09,224.81,1051.62756527,243956.8765829537,2020-02-28
73 | 1582934400,227.81,233.17,214.73,227.0,501.43242224,112207.2429790856,2020-02-29
74 | 1583020800,227.5,232.63,217.51,217.51,730.2477214,162901.2554162427,2020-03-01
75 | 1583107200,217.51,227.63,213.0,219.24,519.20929861,112698.1243903167,2020-03-02
76 | 1583193600,217.79,233.61,217.31,230.78,968.47508051,222437.4772446637,2020-03-03
77 | 1583280000,229.34,231.99,220.5,224.26,161.94188909,36371.6268895336,2020-03-04
78 | 1583366400,223.49,227.75,218.5,223.77,422.23352503,94344.6170386721,2020-03-05
79 | 1583452800,224.71,233.76,224.71,227.02,451.62336291,103804.6998856908,2020-03-06
80 | 1583539200,228.54,245.0,227.82,245.0,167.05617588,39654.6573216785,2020-03-07
81 | 1583625600,245.88,250.78,236.26,237.25,554.73480357,132617.4904974513,2020-03-08
82 | 1583712000,236.72,236.72,195.96,198.82,818.91238126,175399.3047699067,2020-03-09
83 | 1583798400,202.04,207.96,190.52,198.42,700.07995108,140572.4217712675,2020-03-10
84 | 1583884800,201.15,205.0,195.98,201.23,488.73428465,97873.6129219416,2020-03-11
85 | 1583971200,201.54,201.8,182.34,195.05,671.00463566,127363.7193578594,2020-03-12
86 | 1584057600,195.24,195.24,102.0,107.97,7901.27262233,1121880.4051958036,2020-03-13
87 | 1584144000,108.88,142.87,87.57,132.0,15676.12276385,1757308.7126912265,2020-03-14
88 | 1584230400,133.78,133.78,120.0,120.0,3023.67463617,369098.7337948428,2020-03-15
89 | 1584316800,120.17,133.43,107.0,124.05,6774.56771543,801102.1645652949,2020-03-16
90 | 1584403200,121.91,123.32,99.91,110.55,2578.51158841,281298.5041143531,2020-03-17
91 | 1584489600,111.43,120.24,110.32,116.79,1253.55361148,147417.7463989634,2020-03-18
92 | 1584576000,116.77,118.06,110.49,118.06,574.81342128,65992.5585556781,2020-03-19
93 | 1584662400,119.66,142.5,116.02,137.41,2178.89624015,287559.7898686507,2020-03-20
94 | 1584748800,137.38,151.73,116.76,130.8,2927.55270689,405022.427433006,2020-03-21
95 | 1584835200,133.71,137.44,126.74,132.0,731.54305589,96415.5312187322,2020-03-22
96 | 1584921600,132.42,135.4,121.19,122.1,2109.8093475,268132.3685496638,2020-03-23
97 | 1585008000,122.37,137.33,120.27,134.54,663.4853531,86198.3451873637,2020-03-24
98 | 1585094400,136.72,143.16,132.62,140.0,2072.40848606,287612.0788748473,2020-03-25
99 | 1585180800,138.0,141.7,133.13,135.92,902.87922145,123030.5648876125,2020-03-26
100 | 1585267200,136.0,138.95,133.77,138.47,555.21802342,75530.9635760182,2020-03-27
101 | 1585353600,139.92,141.26,130.14,131.31,838.50154211,114303.6760275914,2020-03-28
102 | 1585440000,131.19,132.69,124.0,131.2,804.03547089,102601.4114245475,2020-03-29
103 | 1585526400,130.17,130.31,122.7,125.43,726.90014394,92739.2624944654,2020-03-30
104 | 1585612800,125.3,135.01,121.0,134.77,1840.37667911,240729.1293563581,2020-03-31
105 | 1585699200,131.89,135.0,130.78,133.28,800.91983284,107211.0045622847,2020-04-01
106 | 1585785600,132.52,136.54,129.26,135.69,1066.6716504,141200.0490089721,2020-04-02
107 | 1585872000,137.53,150.0,135.61,140.72,1418.72424713,203455.0021554325,2020-04-03
108 | 1585958400,141.48,145.66,138.43,141.17,568.59731708,80805.398112204,2020-04-04
109 | 1586044800,141.54,145.75,139.94,144.18,278.96639043,39987.6903403026,2020-04-05
110 | 1586131200,145.57,145.58,141.0,143.45,184.83706543,26452.612824971,2020-04-06
111 | 1586217600,144.0,172.62,135.0,171.5,4209.79010347,663668.4111854386,2020-04-07
112 | 1586304000,171.2,175.59,162.9,164.11,2327.96412362,392624.5717093847,2020-04-08
113 | 1586390400,163.81,173.98,163.81,172.99,940.90740436,161125.2039248705,2020-04-09
114 | 1586476800,173.3,173.35,165.04,170.67,289.76226743,49280.3255881057,2020-04-10
115 | 1586563200,168.2,168.22,152.89,157.69,2588.31953075,411333.3402489597,2020-04-11
116 | 1586649600,158.34,160.95,154.19,158.34,908.59805548,143319.8951505978,2020-04-12
117 | 1586736000,158.99,164.91,156.09,158.99,813.34573995,131619.8694696453,2020-04-13
118 | 1586822400,158.34,158.34,149.66,156.41,1219.41242161,188714.4932509103,2020-04-14
119 | 1586908800,156.53,161.61,155.85,157.69,477.14680008,75465.5636302857,2020-04-15
120 | 1586995200,157.05,161.22,152.22,152.86,1434.25489739,225184.0475168002,2020-04-16
121 | 1587081600,152.0,175.0,149.5,172.02,1077.16161859,179807.3729419691,2020-04-17
122 | 1587168000,172.56,173.98,165.23,171.15,1049.95449642,177837.1946823374,2020-04-18
123 | 1587254400,170.8,188.8,170.8,186.81,1243.18139574,222010.2964437254,2020-04-19
124 | 1587340800,187.45,188.04,176.36,180.14,1484.66445786,269845.1625883125,2020-04-20
125 | 1587427200,181.88,185.8,167.6,171.7,996.0776692,178482.8620654193,2020-04-21
126 | 1587513600,172.32,183.54,168.61,170.5,884.85792262,153267.1509932838,2020-04-22
127 | 1587600000,170.84,183.5,170.84,182.25,964.03507999,173939.2822302252,2020-04-23
128 | 1587686400,183.0,193.85,179.23,183.93,1334.3095306,248173.8666428111,2020-04-24
129 | 1587772800,186.5,189.44,182.82,187.5,1003.23649984,188818.2535967484,2020-04-25
130 | 1587859200,187.29,197.61,187.03,194.0,793.34668145,153852.409179305,2020-04-26
131 | 1587945600,194.08,199.48,192.99,196.79,646.52297974,127249.9026379643,2020-04-27
132 | 1588032000,197.56,198.8,190.0,195.97,304.76925965,59461.1704303532,2020-04-28
133 | 1588118400,196.28,197.32,192.62,195.96,470.43927979,91695.0774570453,2020-04-29
134 | 1588204800,196.46,218.64,171.0,214.94,1953.75201464,410115.8100559241,2020-04-30
135 |
--------------------------------------------------------------------------------
/crypto-examples/flask_app.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import time
3 | import json
4 | import sqlite3
5 | from flask import Flask, g
6 | from flask import render_template
7 |
8 | app = Flask(__name__)
9 |
10 | SLEEP = None
11 | if len(sys.argv) > 2:
12 | print("=" * 60)
13 | try:
14 | SLEEP = int(sys.argv[-1])
15 | print(f"Each request will be delayed by {SLEEP} seconds")
16 | except ValueError:
17 | print("Invalid sleep parameter.")
18 | print("=" * 60)
19 |
20 | @app.before_request
21 | def before_request():
22 | g.db = sqlite3.connect('prices.db')
23 |
24 |
25 | @app.teardown_request
26 | def teardown_request_func(error=None):
27 | g.db.close()
28 |
29 |
30 | @app.route('/')
31 | def index():
32 | return render_template('index.html')
33 |
34 |
35 | @app.route('/exchanges')
36 | def exchanges():
37 | cursor = g.db.execute("SELECT DISTINCT exchange FROM price ORDER BY exchange ASC;")
38 | results = cursor.fetchall()
39 | exchanges = [exc for sub in results for exc in sub]
40 | return json.dumps(exchanges), 200, {'Content-Type': 'application/json'}
41 |
42 |
43 | @app.route('/symbols')
44 | def symbols():
45 | cursor = g.db.execute("SELECT DISTINCT symbol FROM price ORDER BY symbol ASC;")
46 | results = cursor.fetchall()
47 | symbols = [exc for sub in results for exc in sub]
48 | return json.dumps(symbols), 200, {'Content-Type': 'application/json'}
49 |
50 |
51 | @app.route('/price///')
52 | def price(exchange, symbol, date):
53 | time.sleep(.2)
54 | g.db.row_factory = sqlite3.Row
55 | cursor = g.db.execute("""
56 | SELECT exchange, symbol, open, high, low, close, volume, day FROM price
57 | WHERE exchange = ? AND symbol = ? AND day = ?;
58 | """, (exchange, symbol, date))
59 | results = [dict(r) for r in cursor.fetchall()]
60 | result = None
61 | if len(results):
62 | result = results[0]
63 | if SLEEP:
64 | time.sleep(SLEEP)
65 | return json.dumps(result), 200, {'Content-Type': 'application/json'}
66 |
67 |
68 | if __name__ == '__main__':
69 | app.debug = True
70 | app.run()
--------------------------------------------------------------------------------
/crypto-examples/prices.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiagobasulto/pycon-concurrency-tutorial-2020/42db4ef5a7ee15ca2e9d841850b1bacb21e9917a/crypto-examples/prices.db
--------------------------------------------------------------------------------
/crypto-examples/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
16 |
17 |
18 | Crypto Price Examples
19 | Crypto price server
20 |
21 | Available endpoints are:
22 |
23 |
24 |
25 | /exchanges
26 | /symbols
27 | /price/{exchange}/{symbol}/{date}
(date format: '2019-12-31')
28 |
29 |
30 | Test the endpoint:
31 |
45 | Loading price...
46 | Sorry, no price for this day...
47 |
48 | Results:
49 |
50 |
51 | Open: $100
52 | High: $100
53 | Low: $100
54 | Close: $100
55 |
56 |
60 |
116 |
117 |
--------------------------------------------------------------------------------
/data/prime_mixture.txt:
--------------------------------------------------------------------------------
1 | 15492781
2 | 15492787
3 | 15492803
4 | 15492811
5 | 15492810
6 | 15492833
7 | 15492859
8 | 15502547
9 | 15520301
10 | 15527509
--------------------------------------------------------------------------------
/img/Deadlock_at_a_four-way-stop.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiagobasulto/pycon-concurrency-tutorial-2020/42db4ef5a7ee15ca2e9d841850b1bacb21e9917a/img/Deadlock_at_a_four-way-stop.gif
--------------------------------------------------------------------------------
/img/cpu_data.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiagobasulto/pycon-concurrency-tutorial-2020/42db4ef5a7ee15ca2e9d841850b1bacb21e9917a/img/cpu_data.png
--------------------------------------------------------------------------------
/img/deadlocks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiagobasulto/pycon-concurrency-tutorial-2020/42db4ef5a7ee15ca2e9d841850b1bacb21e9917a/img/deadlocks.png
--------------------------------------------------------------------------------
/img/gil_meme.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiagobasulto/pycon-concurrency-tutorial-2020/42db4ef5a7ee15ca2e9d841850b1bacb21e9917a/img/gil_meme.jpeg
--------------------------------------------------------------------------------
/img/multiple_process.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiagobasulto/pycon-concurrency-tutorial-2020/42db4ef5a7ee15ca2e9d841850b1bacb21e9917a/img/multiple_process.png
--------------------------------------------------------------------------------
/img/parallel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiagobasulto/pycon-concurrency-tutorial-2020/42db4ef5a7ee15ca2e9d841850b1bacb21e9917a/img/parallel.png
--------------------------------------------------------------------------------
/img/producer-consumer-model.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiagobasulto/pycon-concurrency-tutorial-2020/42db4ef5a7ee15ca2e9d841850b1bacb21e9917a/img/producer-consumer-model.png
--------------------------------------------------------------------------------
/img/recording_studio_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiagobasulto/pycon-concurrency-tutorial-2020/42db4ef5a7ee15ca2e9d841850b1bacb21e9917a/img/recording_studio_light.png
--------------------------------------------------------------------------------
/img/thistall.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiagobasulto/pycon-concurrency-tutorial-2020/42db4ef5a7ee15ca2e9d841850b1bacb21e9917a/img/thistall.jpg
--------------------------------------------------------------------------------
/img/thread_shared_data.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiagobasulto/pycon-concurrency-tutorial-2020/42db4ef5a7ee15ca2e9d841850b1bacb21e9917a/img/thread_shared_data.png
--------------------------------------------------------------------------------
/img/thread_states.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santiagobasulto/pycon-concurrency-tutorial-2020/42db4ef5a7ee15ca2e9d841850b1bacb21e9917a/img/thread_states.png
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "pycon-concurrency-tutorial-2020"
3 | version = "0.1.0"
4 | description = ""
5 | authors = ["Santiago Basulto "]
6 |
7 | [tool.poetry.dependencies]
8 | python = "^3.8"
9 | jupyterlab = "^2.1.1"
10 | flask = "^1.1.2"
11 | pandas = "^1.0.3"
12 | psutil = "^5.7.0"
13 | python-parallel = "^0.9.0"
14 |
15 | [tool.poetry.dev-dependencies]
16 | ipython = "^7.13.0"
17 |
18 | [build-system]
19 | requires = ["poetry>=0.12"]
20 | build-backend = "poetry.masonry.api"
21 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | appnope==0.1.0; sys_platform == "darwin" or platform_system == "Darwin" \
2 | --hash=sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0 \
3 | --hash=sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71
4 | attrs==19.3.0 \
5 | --hash=sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c \
6 | --hash=sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72
7 | backcall==0.1.0 \
8 | --hash=sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4 \
9 | --hash=sha256:bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2
10 | bleach==3.1.4 \
11 | --hash=sha256:cc8da25076a1fe56c3ac63671e2194458e0c4d9c7becfd52ca251650d517903c \
12 | --hash=sha256:e78e426105ac07026ba098f04de8abe9b6e3e98b5befbf89b51a5ef0a4292b03
13 | certifi==2020.4.5.1 \
14 | --hash=sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304 \
15 | --hash=sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519
16 | chardet==3.0.4 \
17 | --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 \
18 | --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae
19 | click==7.1.2 \
20 | --hash=sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc \
21 | --hash=sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a
22 | colorama==0.4.3; sys_platform == "win32" \
23 | --hash=sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff \
24 | --hash=sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1
25 | decorator==4.4.2 \
26 | --hash=sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760 \
27 | --hash=sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7
28 | defusedxml==0.6.0 \
29 | --hash=sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93 \
30 | --hash=sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5
31 | entrypoints==0.3 \
32 | --hash=sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19 \
33 | --hash=sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451
34 | flask==1.1.2 \
35 | --hash=sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557 \
36 | --hash=sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060
37 | idna==2.9 \
38 | --hash=sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa \
39 | --hash=sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb
40 | ipykernel==5.2.1 \
41 | --hash=sha256:003c9c1ab6ff87d11f531fee2b9ca59affab19676fc6b2c21da329aef6e73499 \
42 | --hash=sha256:2937373c356fa5b634edb175c5ea0e4b25de8008f7c194f2d49cfbd1f9c970a8
43 | ipython==7.13.0 \
44 | --hash=sha256:eb8d075de37f678424527b5ef6ea23f7b80240ca031c2dd6de5879d687a65333 \
45 | --hash=sha256:ca478e52ae1f88da0102360e57e528b92f3ae4316aabac80a2cd7f7ab2efb48a
46 | ipython-genutils==0.2.0 \
47 | --hash=sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8 \
48 | --hash=sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8
49 | itsdangerous==1.1.0 \
50 | --hash=sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749 \
51 | --hash=sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19
52 | jedi==0.16.0 \
53 | --hash=sha256:b4f4052551025c6b0b0b193b29a6ff7bdb74c52450631206c262aef9f7159ad2 \
54 | --hash=sha256:d5c871cb9360b414f981e7072c52c33258d598305280fef91c6cae34739d65d5
55 | jinja2==2.11.2 \
56 | --hash=sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035 \
57 | --hash=sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0
58 | json5==0.9.4 \
59 | --hash=sha256:4e0fc461b5508196a3ddb3b981dc677805923b86d6eb603c7f58f2459ab1458f \
60 | --hash=sha256:2ebfad1cd502dca6aecab5b5c36a21c732c3461ddbc412fb0e9a52b07ddfe586
61 | jsonschema==3.2.0 \
62 | --hash=sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163 \
63 | --hash=sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a
64 | jupyter-client==6.1.3 \
65 | --hash=sha256:cde8e83aab3ec1c614f221ae54713a9a46d3bf28292609d2db1b439bef5a8c8e \
66 | --hash=sha256:3a32fa4d0b16d1c626b30c3002a62dfd86d6863ed39eaba3f537fade197bb756
67 | jupyter-core==4.6.3 \
68 | --hash=sha256:a4ee613c060fe5697d913416fc9d553599c05e4492d58fac1192c9a6844abb21 \
69 | --hash=sha256:394fd5dd787e7c8861741880bdf8a00ce39f95de5d18e579c74b882522219e7e
70 | jupyterlab==2.1.1 \
71 | --hash=sha256:36948d2a42376e186cb5358076d0c3c49b98dfed006c99d0c98561d0e491c4fa \
72 | --hash=sha256:2dec7e509ffa3892c67d9ac7c89d7c6a860c151d969d89420d9e20e753b2fb29
73 | jupyterlab-server==1.1.1 \
74 | --hash=sha256:f678a77fa74eeec80c15d6c9884f6ff050f5473267bd342944164115768ec759 \
75 | --hash=sha256:b5921872746b1ca109d1852196ed11e537f8aad67a1933c1d4a8ac63f8e61cca
76 | markupsafe==1.1.1 \
77 | --hash=sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161 \
78 | --hash=sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7 \
79 | --hash=sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183 \
80 | --hash=sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b \
81 | --hash=sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e \
82 | --hash=sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f \
83 | --hash=sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1 \
84 | --hash=sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5 \
85 | --hash=sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1 \
86 | --hash=sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735 \
87 | --hash=sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21 \
88 | --hash=sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235 \
89 | --hash=sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b \
90 | --hash=sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f \
91 | --hash=sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905 \
92 | --hash=sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1 \
93 | --hash=sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d \
94 | --hash=sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff \
95 | --hash=sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473 \
96 | --hash=sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e \
97 | --hash=sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66 \
98 | --hash=sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5 \
99 | --hash=sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d \
100 | --hash=sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e \
101 | --hash=sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6 \
102 | --hash=sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2 \
103 | --hash=sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c \
104 | --hash=sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b
105 | mistune==0.8.4 \
106 | --hash=sha256:88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4 \
107 | --hash=sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e
108 | nbconvert==5.6.1 \
109 | --hash=sha256:f0d6ec03875f96df45aa13e21fd9b8450c42d7e1830418cccc008c0df725fcee \
110 | --hash=sha256:21fb48e700b43e82ba0e3142421a659d7739b65568cc832a13976a77be16b523
111 | nbformat==5.0.6 \
112 | --hash=sha256:276343c78a9660ab2a63c28cc33da5f7c58c092b3f3a40b6017ae2ce6689320d \
113 | --hash=sha256:049af048ed76b95c3c44043620c17e56bc001329e07f83fec4f177f0e3d7b757
114 | notebook==6.0.3 \
115 | --hash=sha256:3edc616c684214292994a3af05eaea4cc043f6b4247d830f3a2f209fa7639a80 \
116 | --hash=sha256:47a9092975c9e7965ada00b9a20f0cf637d001db60d241d479f53c0be117ad48
117 | numpy==1.18.3 \
118 | --hash=sha256:a6bc9432c2640b008d5f29bad737714eb3e14bb8854878eacf3d7955c4e91c36 \
119 | --hash=sha256:48e15612a8357393d176638c8f68a19273676877caea983f8baf188bad430379 \
120 | --hash=sha256:eb2286249ebfe8fcb5b425e5ec77e4736d53ee56d3ad296f8947f67150f495e3 \
121 | --hash=sha256:1e37626bcb8895c4b3873fcfd54e9bfc5ffec8d0f525651d6985fcc5c6b6003c \
122 | --hash=sha256:163c78c04f47f26ca1b21068cea25ed7c5ecafe5f5ab2ea4895656a750582b56 \
123 | --hash=sha256:3d9e1554cd9b5999070c467b18e5ae3ebd7369f02706a8850816f576a954295f \
124 | --hash=sha256:40c24960cd5cec55222963f255858a1c47c6fa50a65a5b03fd7de75e3700eaaa \
125 | --hash=sha256:a551d8cc267c634774830086da42e4ba157fa41dd3b93982bc9501b284b0c689 \
126 | --hash=sha256:0aa2b318cf81eb1693fcfcbb8007e95e231d7e1aa24288137f3b19905736c3ee \
127 | --hash=sha256:a41f303b3f9157a31ce7203e3ca757a0c40c96669e72d9b6ee1bce8507638970 \
128 | --hash=sha256:e607b8cdc2ae5d5a63cd1bec30a15b5ed583ac6a39f04b7ba0f03fcfbf29c05b \
129 | --hash=sha256:fdee7540d12519865b423af411bd60ddb513d2eb2cd921149b732854995bbf8b \
130 | --hash=sha256:6725d2797c65598778409aba8cd67077bb089d5b7d3d87c2719b206dc84ec05e \
131 | --hash=sha256:4847f0c993298b82fad809ea2916d857d0073dc17b0510fbbced663b3265929d \
132 | --hash=sha256:46f404314dbec78cb342904f9596f25f9b16e7cf304030f1339e553c8e77f51c \
133 | --hash=sha256:264fd15590b3f02a1fbc095e7e1f37cdac698ff3829e12ffdcffdce3772f9d44 \
134 | --hash=sha256:e94a39d5c40fffe7696009dbd11bc14a349b377e03a384ed011e03d698787dd3 \
135 | --hash=sha256:a4305564e93f5c4584f6758149fd446df39fd1e0a8c89ca0deb3cce56106a027 \
136 | --hash=sha256:99f0ba97e369f02a21bb95faa3a0de55991fd5f0ece2e30a9e2eaebeac238921 \
137 | --hash=sha256:c60175d011a2e551a2f74c84e21e7c982489b96b6a5e4b030ecdeacf2914da68 \
138 | --hash=sha256:e46e2384209c91996d5ec16744234d1c906ab79a701ce1a26155c9ec890b8dc8
139 | pandas==1.0.3 \
140 | --hash=sha256:d234bcf669e8b4d6cbcd99e3ce7a8918414520aeb113e2a81aeb02d0a533d7f7 \
141 | --hash=sha256:ca84a44cf727f211752e91eab2d1c6c1ab0f0540d5636a8382a3af428542826e \
142 | --hash=sha256:1fa4bae1a6784aa550a1c9e168422798104a85bf9c77a1063ea77ee6f8452e3a \
143 | --hash=sha256:863c3e4b7ae550749a0bb77fa22e601a36df9d2905afef34a6965bed092ba9e5 \
144 | --hash=sha256:a210c91a02ec5ff05617a298ad6f137b9f6f5771bf31f2d6b6367d7f71486639 \
145 | --hash=sha256:11c7cb654cd3a0e9c54d81761b5920cdc86b373510d829461d8f2ed6d5905266 \
146 | --hash=sha256:6597df07ea361231e60c00692d8a8099b519ed741c04e65821e632bc9ccb924c \
147 | --hash=sha256:743bba36e99d4440403beb45a6f4f3a667c090c00394c176092b0b910666189b \
148 | --hash=sha256:07c1b58936b80eafdfe694ce964ac21567b80a48d972879a359b3ebb2ea76835 \
149 | --hash=sha256:12f492dd840e9db1688126216706aa2d1fcd3f4df68a195f9479272d50054645 \
150 | --hash=sha256:0ebe327fb088df4d06145227a4aa0998e4f80a9e6aed4b61c1f303bdfdf7c722 \
151 | --hash=sha256:858a0d890d957ae62338624e4aeaf1de436dba2c2c0772570a686eaca8b4fc85 \
152 | --hash=sha256:387dc7b3c0424327fe3218f81e05fc27832772a5dffbed385013161be58df90b \
153 | --hash=sha256:167a1315367cea6ec6a5e11e791d9604f8e03f95b57ad227409de35cf850c9c5 \
154 | --hash=sha256:1a7c56f1df8d5ad8571fa251b864231f26b47b59cbe41aa5c0983d17dbb7a8e4 \
155 | --hash=sha256:32f42e322fb903d0e189a4c10b75ba70d90958cc4f66a1781ed027f1a1d14586
156 | pandocfilters==1.4.2 \
157 | --hash=sha256:b3dd70e169bb5449e6bc6ff96aea89c5eea8c5f6ab5e207fc2f521a2cf4a0da9
158 | parso==0.6.2 \
159 | --hash=sha256:8515fc12cfca6ee3aa59138741fc5624d62340c97e401c74875769948d4f2995 \
160 | --hash=sha256:0c5659e0c6eba20636f99a04f469798dca8da279645ce5c387315b2c23912157
161 | pexpect==4.8.0; sys_platform != "win32" \
162 | --hash=sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937 \
163 | --hash=sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c
164 | pickleshare==0.7.5 \
165 | --hash=sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56 \
166 | --hash=sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca
167 | prometheus-client==0.7.1 \
168 | --hash=sha256:71cd24a2b3eb335cb800c7159f423df1bd4dcd5171b234be15e3f31ec9f622da
169 | prompt-toolkit==3.0.5 \
170 | --hash=sha256:df7e9e63aea609b1da3a65641ceaf5bc7d05e0a04de5bd45d05dbeffbabf9e04 \
171 | --hash=sha256:563d1a4140b63ff9dd587bda9557cffb2fe73650205ab6f4383092fb882e7dc8
172 | psutil==5.7.0 \
173 | --hash=sha256:298af2f14b635c3c7118fd9183843f4e73e681bb6f01e12284d4d70d48a60953 \
174 | --hash=sha256:75e22717d4dbc7ca529ec5063000b2b294fc9a367f9c9ede1f65846c7955fd38 \
175 | --hash=sha256:f344ca230dd8e8d5eee16827596f1c22ec0876127c28e800d7ae20ed44c4b310 \
176 | --hash=sha256:e2d0c5b07c6fe5a87fa27b7855017edb0d52ee73b71e6ee368fae268605cc3f5 \
177 | --hash=sha256:a02f4ac50d4a23253b68233b07e7cdb567bd025b982d5cf0ee78296990c22d9e \
178 | --hash=sha256:1413f4158eb50e110777c4f15d7c759521703bd6beb58926f1d562da40180058 \
179 | --hash=sha256:d008ddc00c6906ec80040d26dc2d3e3962109e40ad07fd8a12d0284ce5e0e4f8 \
180 | --hash=sha256:73f35ab66c6c7a9ce82ba44b1e9b1050be2a80cd4dcc3352cc108656b115c74f \
181 | --hash=sha256:60b86f327c198561f101a92be1995f9ae0399736b6eced8f24af41ec64fb88d4 \
182 | --hash=sha256:d84029b190c8a66a946e28b4d3934d2ca1528ec94764b180f7d6ea57b0e75e26 \
183 | --hash=sha256:685ec16ca14d079455892f25bd124df26ff9137664af445563c1bd36629b5e0e
184 | ptyprocess==0.6.0; sys_platform != "win32" or os_name != "nt" \
185 | --hash=sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f \
186 | --hash=sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0
187 | pygments==2.6.1 \
188 | --hash=sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324 \
189 | --hash=sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44
190 | pyrsistent==0.16.0 \
191 | --hash=sha256:28669905fe725965daa16184933676547c5bb40a5153055a8dee2a4bd7933ad3
192 | python-dateutil==2.8.1 \
193 | --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \
194 | --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a
195 | python-parallel==0.9.0 \
196 | --hash=sha256:3e50bfa089d57910cd8bc88996dfb09c2007e450381a01b23e062ef95e66dee1 \
197 | --hash=sha256:d681b5f011216acb4c082fe28833698dbee63f9fae537d4137f2e7ba2044c617
198 | pytz==2020.1 \
199 | --hash=sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed \
200 | --hash=sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048
201 | pywin32==227; sys_platform == "win32" \
202 | --hash=sha256:371fcc39416d736401f0274dd64c2302728c9e034808e37381b5e1b22be4a6b0 \
203 | --hash=sha256:4cdad3e84191194ea6d0dd1b1b9bdda574ff563177d2adf2b4efec2a244fa116 \
204 | --hash=sha256:f4c5be1a293bae0076d93c88f37ee8da68136744588bc5e2be2f299a34ceb7aa \
205 | --hash=sha256:a929a4af626e530383a579431b70e512e736e9588106715215bf685a3ea508d4 \
206 | --hash=sha256:300a2db938e98c3e7e2093e4491439e62287d0d493fe07cce110db070b54c0be \
207 | --hash=sha256:9b31e009564fb95db160f154e2aa195ed66bcc4c058ed72850d047141b36f3a2 \
208 | --hash=sha256:47a3c7551376a865dd8d095a98deba954a98f326c6fe3c72d8726ca6e6b15507 \
209 | --hash=sha256:31f88a89139cb2adc40f8f0e65ee56a8c585f629974f9e07622ba80199057511 \
210 | --hash=sha256:7f18199fbf29ca99dff10e1f09451582ae9e372a892ff03a28528a24d55875bc \
211 | --hash=sha256:7c1ae32c489dc012930787f06244426f8356e129184a02c25aef163917ce158e \
212 | --hash=sha256:c054c52ba46e7eb6b7d7dfae4dbd987a1bb48ee86debe3f245a2884ece46e295 \
213 | --hash=sha256:f27cec5e7f588c3d1051651830ecc00294f90728d19c3bf6916e6dba93ea357c
214 | pywinpty==0.5.7; os_name == "nt" \
215 | --hash=sha256:b358cb552c0f6baf790de375fab96524a0498c9df83489b8c23f7f08795e966b \
216 | --hash=sha256:1e525a4de05e72016a7af27836d512db67d06a015aeaf2fa0180f8e6a039b3c2 \
217 | --hash=sha256:2740eeeb59297593a0d3f762269b01d0285c1b829d6827445fcd348fb47f7e70 \
218 | --hash=sha256:33df97f79843b2b8b8bc5c7aaf54adec08cc1bae94ee99dfb1a93c7a67704d95 \
219 | --hash=sha256:e854211df55d107f0edfda8a80b39dfc87015bef52a8fe6594eb379240d81df2 \
220 | --hash=sha256:dbd838de92de1d4ebf0dce9d4d5e4fc38d0b7b1de837947a18b57a882f219139 \
221 | --hash=sha256:5fb2c6c6819491b216f78acc2c521b9df21e0f53b9a399d58a5c151a3c4e2a2d \
222 | --hash=sha256:dd22c8efacf600730abe4a46c1388355ce0d4ab75dc79b15d23a7bd87bf05b48 \
223 | --hash=sha256:8fc5019ff3efb4f13708bd3b5ad327589c1a554cb516d792527361525a7cb78c \
224 | --hash=sha256:2d7e9c881638a72ffdca3f5417dd1563b60f603e1b43e5895674c2a1b01f95a0
225 | pyzmq==19.0.0 \
226 | --hash=sha256:3f12ce1e9cc9c31497bd82b207e8e86ccda9eebd8c9f95053aae46d15ccd2196 \
227 | --hash=sha256:e8e4efb52ec2df8d046395ca4c84ae0056cf507b2f713ec803c65a8102d010de \
228 | --hash=sha256:f5b6d015587a1d6f582ba03b226a9ddb1dfb09878b3be04ef48b01b7d4eb6b2a \
229 | --hash=sha256:bb10361293d96aa92be6261fa4d15476bca56203b3a11c62c61bd14df0ef89ba \
230 | --hash=sha256:4557d5e036e6d85715b4b9fdb482081398da1d43dc580d03db642b91605b409f \
231 | --hash=sha256:84b91153102c4bcf5d0f57d1a66a0f03c31e9e6525a5f656f52fc615a675c748 \
232 | --hash=sha256:6aaaf90b420dc40d9a0e1996b82c6a0ff91d9680bebe2135e67c9e6d197c0a53 \
233 | --hash=sha256:ad48865a29efa8a0cecf266432ea7bc34e319954e55cf104be0319c177e6c8f5 \
234 | --hash=sha256:32234c21c5e0a767c754181c8112092b3ddd2e2a36c3f76fc231ced817aeee47 \
235 | --hash=sha256:f37c29da2a5b0c5e31e6f8aab885625ea76c807082f70b2d334d3fd573c3100a \
236 | --hash=sha256:1e076ad5bd3638a18c376544d32e0af986ca10d43d4ce5a5d889a8649f0d0a3d \
237 | --hash=sha256:f4d558bc5668d2345773a9ff8c39e2462dafcb1f6772a2e582fbced389ce527f \
238 | --hash=sha256:4f562dab21c03c7aa061f63b147a595dbe1006bf4f03213272fc9f7d5baec791 \
239 | --hash=sha256:7f7e7b24b1d392bb5947ba91c981e7d1a43293113642e0d8870706c8e70cdc71 \
240 | --hash=sha256:75238d3c16cab96947705d5709187a49ebb844f54354cdf0814d195dd4c045de \
241 | --hash=sha256:cb3b7156ef6b1a119e68fbe3a54e0a0c40ecacc6b7838d57dd708c90b62a06dc \
242 | --hash=sha256:a99ae601b4f6917985e9bb071549e30b6f93c72f5060853e197bdc4b7d357e5f \
243 | --hash=sha256:242d949eb6b10197cda1d1cec377deab1d5324983d77e0d0bf9dc5eb6d71a6b4 \
244 | --hash=sha256:a49fd42a29c1cc1aa9f461c5f2f5e0303adba7c945138b35ee7f4ab675b9f754 \
245 | --hash=sha256:5f10a31f288bf055be76c57710807a8f0efdb2b82be6c2a2b8f9a61f33a40cea \
246 | --hash=sha256:26f4ae420977d2a8792d7c2d7bda43128b037b5eeb21c81951a94054ad8b8843 \
247 | --hash=sha256:944f6bb5c63140d76494467444fd92bebd8674236837480a3c75b01fe17df1ab \
248 | --hash=sha256:b08e425cf93b4e018ab21dc8fdbc25d7d0502a23cc4fea2380010cf8cf11e462 \
249 | --hash=sha256:a1f957c20c9f51d43903881399b078cddcf710d34a2950e88bce4e494dcaa4d1 \
250 | --hash=sha256:bd1a769d65257a7a12e2613070ca8155ee348aa9183f2aadf1c8b8552a5510f5 \
251 | --hash=sha256:0bbc1728fe4314b4ca46249c33873a390559edac7c217ec7001b5e0c34a8fb7f \
252 | --hash=sha256:5e071b834051e9ecb224915398f474bfad802c2fff883f118ff5363ca4ae3edf \
253 | --hash=sha256:5e1f65e576ab07aed83f444e201d86deb01cd27dcf3f37c727bc8729246a60a8
254 | requests==2.23.0 \
255 | --hash=sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee \
256 | --hash=sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6
257 | send2trash==1.5.0 \
258 | --hash=sha256:f1691922577b6fa12821234aeb57599d887c4900b9ca537948d2dac34aea888b \
259 | --hash=sha256:60001cc07d707fe247c94f74ca6ac0d3255aabcb930529690897ca2a39db28b2
260 | six==1.14.0 \
261 | --hash=sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c \
262 | --hash=sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a
263 | terminado==0.8.3 \
264 | --hash=sha256:a43dcb3e353bc680dd0783b1d9c3fc28d529f190bc54ba9a229f72fe6e7a54d7 \
265 | --hash=sha256:4804a774f802306a7d9af7322193c5390f1da0abb429e082a10ef1d46e6fb2c2
266 | testpath==0.4.4 \
267 | --hash=sha256:bfcf9411ef4bf3db7579063e0546938b1edda3d69f4e1fb8756991f5951f85d4 \
268 | --hash=sha256:60e0a3261c149755f4399a1fff7d37523179a70fdc3abdf78de9fc2604aeec7e
269 | tornado==6.0.4 \
270 | --hash=sha256:5217e601700f24e966ddab689f90b7ea4bd91ff3357c3600fa1045e26d68e55d \
271 | --hash=sha256:c98232a3ac391f5faea6821b53db8db461157baa788f5d6222a193e9456e1740 \
272 | --hash=sha256:5f6a07e62e799be5d2330e68d808c8ac41d4a259b9cea61da4101b83cb5dc673 \
273 | --hash=sha256:c952975c8ba74f546ae6de2e226ab3cc3cc11ae47baf607459a6728585bb542a \
274 | --hash=sha256:2c027eb2a393d964b22b5c154d1a23a5f8727db6fda837118a776b29e2b8ebc6 \
275 | --hash=sha256:5618f72e947533832cbc3dec54e1dffc1747a5cb17d1fd91577ed14fa0dc081b \
276 | --hash=sha256:22aed82c2ea340c3771e3babc5ef220272f6fd06b5108a53b4976d0d722bcd52 \
277 | --hash=sha256:c58d56003daf1b616336781b26d184023ea4af13ae143d9dda65e31e534940b9 \
278 | --hash=sha256:0fe2d45ba43b00a41cd73f8be321a44936dc1aba233dee979f17a042b83eb6dc
279 | traitlets==4.3.3 \
280 | --hash=sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44 \
281 | --hash=sha256:d023ee369ddd2763310e4c3eae1ff649689440d4ae59d7485eb4cfbbe3e359f7
282 | urllib3==1.25.9 \
283 | --hash=sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115 \
284 | --hash=sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527
285 | wcwidth==0.1.9 \
286 | --hash=sha256:cafe2186b3c009a04067022ce1dcd79cb38d8d65ee4f4791b8888d6599d1bbe1 \
287 | --hash=sha256:ee73862862a156bf77ff92b09034fc4825dd3af9cf81bc5b360668d425f3c5f1
288 | webencodings==0.5.1 \
289 | --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \
290 | --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923
291 | werkzeug==1.0.1 \
292 | --hash=sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43 \
293 | --hash=sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c
294 |
--------------------------------------------------------------------------------
/sleep_n_seconds.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import time
3 |
4 | if __name__ == "__main__":
5 | if len(sys.argv) != 2:
6 | print("Bad usage! Try: python sleep_n_seconds.py N (seconds to sleep)")
7 | exit()
8 | time.sleep(int(sys.argv[1]))
--------------------------------------------------------------------------------