<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>lostleaf.eth</title>
        <link>https://paragraph.com/@lostleaf</link>
        <description>Quant Trader / HODLER
</description>
        <lastBuildDate>Tue, 07 Apr 2026 09:10:01 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>lostleaf.eth</title>
            <url>https://storage.googleapis.com/papyrus_images/87a138698a35764735576a3420eacdcb1c3f5a1d626b761e71103337fce4c377.png</url>
            <link>https://paragraph.com/@lostleaf</link>
        </image>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[Switching from iTerm2 to Ghostty]]></title>
            <link>https://paragraph.com/@lostleaf/switching-from-iterm2-to-ghostty</link>
            <guid>KQXPlpzbZ2J036EphZlE</guid>
            <pubDate>Wed, 18 Mar 2026 02:24:01 GMT</pubDate>
            <description><![CDATA[I've been using iTerm2 for years, but recently switched to Ghostty — and I'm not going back...]]></description>
            <content:encoded><![CDATA[<p>I've been using iTerm2 for years, but recently switched to <strong>Ghostty</strong> — and I'm not going back.</p><p>The nudge came from <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/zhangchitc">zhangchitc</a>, who shared a polished Ghostty config along with a relevant data point: Boris, the lead on Claude Code, has said his team's go-to terminal is Ghostty — running five instances in parallel. If the people building Claude Code are running it in Ghostty, that's a reasonable signal worth paying attention to.</p><p>zhangchitc open-sourced his config, I ran it for a few days, adjusted it to fit my workflow, and here's where I landed.</p><h2 id="h-what-i-changed" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">What I Changed</h2><p>zhangchitc's config is already well thought-out — the changes I made are purely personal preference:</p><ul><li><p><strong>Font</strong>: <code>Maple Mono NF CN</code> → <code>Monaco Nerd Font Mono</code> — just find Monaco easier on the eyes for code</p></li><li><p><strong>Theme</strong>: <code>Catppuccin Mocha</code> → <code>Selenized Black</code> — pure black background, high contrast, easier to read logs for long stretches</p></li><li><p><strong>No background transparency</strong>: zhangchitc uses <code>background-opacity = 0.85</code> for a frosted-glass look, which is nice, but I prefer a solid background for reading focus</p></li><li><p><strong>Cursor</strong>: non-blinking block cursor (<code>block</code> + <code>blink = false</code>), with <code>shell-integration-features = no-cursor</code> to prevent shell integration from overriding it</p></li><li><p><strong>Removed Ghostty's native splits</strong>: my workflow puts different SSH connections in separate Ghostty tabs, with tmux handling splits on the remote side — so the <code>cmd+d</code> split keybinds are gone</p></li><li><p><strong>No window state persistence</strong>: dropped <code>window-save-state = always</code>, so each launch starts clean</p></li></ul><blockquote><p>Net result: pure black background, solid block cursor, no transparency, no native splits — as minimal as a terminal gets.</p></blockquote><h2 id="h-why-your-terminal-actually-matters" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Why Your Terminal Actually Matters</h2><p>The terminal is one of those tools you use every day but rarely optimize. A few areas where Ghostty genuinely helps:</p><ul><li><p><strong>GPU rendering</strong>: when backtests are dumping large volumes of log output, iTerm2 can stutter. Ghostty renders on the GPU — scrolling through dense output stays smooth</p></li><li><p><strong>Quick Terminal</strong>: global hotkey <code>Ctrl+`</code> drops a terminal down from the top of the screen from any app. Great for a quick script or a fast <code>git push</code> without switching context</p></li><li><p><strong>Claude Code workflow</strong>: one tab running Claude Code, another SSH'd into a server tailing live logs, Quick Terminal for one-off commands — tab switching is fast and all tabs share a consistent color scheme</p></li><li><p><strong>tmux on the remote</strong>: remote servers use tmux for multi-pane process management (data feeds, strategy processes, monitoring). Ghostty handles local tabs and connections; tmux handles remote layout. They don't step on each other.</p></li></ul><h2 id="h-installation" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Installation</h2><p>On macOS: <code>brew install --cask ghostty</code>. Config goes in <code>~/.config/ghostty/config</code>.</p><p><strong>One macOS gotcha</strong> (possibly a bug): the system config at <code>~/Library/Application Support/com.mitchellh.ghostty/config</code> may contain a line like <code>theme = TokyoNight Night</code>. If it does, comment it out — otherwise it overrides your theme setting in <code>~/.config/ghostty/config</code>, locking you into TokyoNight Night regardless of what you set.</p><h2 id="h-config" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Config</h2><p><strong>My config:</strong></p><pre data-type="codeBlock" text="# ============================================
# Ghostty Terminal - My Config
# ============================================
# Location: ~/.config/ghostty/config
# Hot reload: Cmd+Shift+,
#
# Reference: config shared by zhangchitc
# https://gist.github.com/zhangchitc/7dead7c1b517390e061e07759ed80277
#
# Based on the reference config, adjusted to personal preferences:
# - Font: switched to Monaco Nerd Font Mono
# - Theme: switched to Selenized Black
# - Transparency: no background transparency, solid color for focused reading
# - Split keybinds: removed Ghostty native splits
#                   different SSH connections separated by Ghostty tabs, remote splits handled by tmux
# - Quick Terminal: enabled, globally summon with Ctrl+` anytime, supplements regular terminal windows
# - Window state: not saved

# --- Font &amp; Typography ---
font-family = &quot;Monaco Nerd Font Mono&quot;
font-size = 14

# --- Theme ---
theme = &quot;Selenized Black&quot;
# Increase ANSI faint/dim text opacity to prevent it from being too light on the Selenized Black theme
faint-opacity = 0.8

# --- Window Appearance ---
# No transparency, solid background
window-padding-x = 10
window-padding-y = 8

# --- Cursor ---
# Non-blinking block cursor
cursor-style = block
cursor-style-blink = false
shell-integration-features = no-cursor

# --- Mouse ---
# Hide mouse pointer while typing
mouse-hide-while-typing = true
# Selection is copied directly to system clipboard
copy-on-select = clipboard

# --- Quick Terminal (global dropdown terminal) ---
# Global hotkey Ctrl+` to summon/dismiss from the top of the screen
# Can be triggered from any app, useful for quick commands and git operations
quick-terminal-position = top
# Appears on the screen where the mouse is (multi-monitor friendly)
quick-terminal-screen = mouse
# Auto-hide when clicking another window
quick-terminal-autohide = true
quick-terminal-animation-duration = 0.15

# --- Security ---
# Prevent malicious content from injecting commands via clipboard
clipboard-paste-protection = true
clipboard-paste-bracketed-safe = true

# --- Shell Integration ---
# Auto-detect zsh/bash/fish and inject integration scripts
shell-integration = detect

# --- Performance ---
# ~25MB scrollback buffer
scrollback-limit = 25000000

# --- Keybinds ---
# Tabs (use case: different SSH connections in separate tabs)
keybind = cmd+t=new_tab
keybind = cmd+shift+left=previous_tab
keybind = cmd+shift+right=next_tab
keybind = cmd+w=close_surface

# Quick Terminal global hotkey
keybind = global:ctrl+grave_accent=toggle_quick_terminal

# Font size
keybind = cmd+plus=increase_font_size:1
keybind = cmd+minus=decrease_font_size:1
keybind = cmd+zero=reset_font_size

# Hot reload config file (no restart needed after config changes)
keybind = cmd+shift+comma=reload_config
"><code># <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span>
# Ghostty Terminal <span class="hljs-operator">-</span> My Config
# <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span>
# Location: <span class="hljs-operator">~</span><span class="hljs-operator">/</span>.config/ghostty<span class="hljs-operator">/</span>config
# Hot reload: Cmd<span class="hljs-operator">+</span>Shift<span class="hljs-operator">+</span>,
#
# Reference: config shared by zhangchitc
# https:<span class="hljs-comment">//gist.github.com/zhangchitc/7dead7c1b517390e061e07759ed80277</span>
#
# Based on the reference config, adjusted to personal preferences:
# <span class="hljs-operator">-</span> Font: switched to Monaco Nerd Font Mono
# <span class="hljs-operator">-</span> Theme: switched to Selenized Black
# <span class="hljs-operator">-</span> Transparency: no background transparency, solid color <span class="hljs-keyword">for</span> focused reading
# <span class="hljs-operator">-</span> Split keybinds: removed Ghostty native splits
#                   different SSH connections separated by Ghostty tabs, remote splits handled by tmux
# <span class="hljs-operator">-</span> Quick Terminal: enabled, globally summon with Ctrl<span class="hljs-operator">+</span>` anytime, supplements regular terminal windows
# <span class="hljs-operator">-</span> Window state: not saved

# <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span> Font <span class="hljs-operator">&amp;</span> Typography <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>
font<span class="hljs-operator">-</span>family <span class="hljs-operator">=</span> <span class="hljs-string">"Monaco Nerd Font Mono"</span>
font<span class="hljs-operator">-</span>size <span class="hljs-operator">=</span> <span class="hljs-number">14</span>

# <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span> Theme <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>
theme <span class="hljs-operator">=</span> <span class="hljs-string">"Selenized Black"</span>
# Increase ANSI faint<span class="hljs-operator">/</span>dim text opacity to prevent it <span class="hljs-keyword">from</span> being too light on the Selenized Black theme
faint<span class="hljs-operator">-</span>opacity <span class="hljs-operator">=</span> <span class="hljs-number">0</span><span class="hljs-number">.8</span>

# <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span> Window Appearance <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>
# No transparency, solid background
window<span class="hljs-operator">-</span>padding<span class="hljs-operator">-</span>x <span class="hljs-operator">=</span> <span class="hljs-number">10</span>
window<span class="hljs-operator">-</span>padding<span class="hljs-operator">-</span>y <span class="hljs-operator">=</span> <span class="hljs-number">8</span>

# <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span> Cursor <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>
# Non<span class="hljs-operator">-</span>blinking <span class="hljs-built_in">block</span> cursor
cursor<span class="hljs-operator">-</span>style <span class="hljs-operator">=</span> <span class="hljs-built_in">block</span>
cursor<span class="hljs-operator">-</span>style<span class="hljs-operator">-</span>blink <span class="hljs-operator">=</span> <span class="hljs-literal">false</span>
shell<span class="hljs-operator">-</span>integration<span class="hljs-operator">-</span>features <span class="hljs-operator">=</span> no<span class="hljs-operator">-</span>cursor

# <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span> Mouse <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>
# Hide mouse pointer <span class="hljs-keyword">while</span> typing
mouse<span class="hljs-operator">-</span>hide<span class="hljs-operator">-</span><span class="hljs-keyword">while</span><span class="hljs-operator">-</span>typing <span class="hljs-operator">=</span> <span class="hljs-literal">true</span>
# Selection <span class="hljs-keyword">is</span> copied directly to system clipboard
copy<span class="hljs-operator">-</span>on<span class="hljs-operator">-</span>select <span class="hljs-operator">=</span> clipboard

# <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span> Quick Terminal (<span class="hljs-keyword">global</span> dropdown terminal) <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>
# Global hotkey Ctrl<span class="hljs-operator">+</span>` to summon<span class="hljs-operator">/</span>dismiss <span class="hljs-keyword">from</span> the top of the screen
# Can be triggered <span class="hljs-keyword">from</span> any app, useful <span class="hljs-keyword">for</span> quick commands and git operations
quick<span class="hljs-operator">-</span>terminal<span class="hljs-operator">-</span>position <span class="hljs-operator">=</span> top
# Appears on the screen where the mouse <span class="hljs-keyword">is</span> (multi<span class="hljs-operator">-</span>monitor friendly)
quick<span class="hljs-operator">-</span>terminal<span class="hljs-operator">-</span>screen <span class="hljs-operator">=</span> mouse
# Auto<span class="hljs-operator">-</span>hide when clicking another window
quick<span class="hljs-operator">-</span>terminal<span class="hljs-operator">-</span>autohide <span class="hljs-operator">=</span> <span class="hljs-literal">true</span>
quick<span class="hljs-operator">-</span>terminal<span class="hljs-operator">-</span>animation<span class="hljs-operator">-</span>duration <span class="hljs-operator">=</span> <span class="hljs-number">0</span><span class="hljs-number">.15</span>

# <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span> Security <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>
# Prevent malicious content <span class="hljs-keyword">from</span> injecting commands via clipboard
clipboard<span class="hljs-operator">-</span>paste<span class="hljs-operator">-</span>protection <span class="hljs-operator">=</span> <span class="hljs-literal">true</span>
clipboard<span class="hljs-operator">-</span>paste<span class="hljs-operator">-</span>bracketed<span class="hljs-operator">-</span>safe <span class="hljs-operator">=</span> <span class="hljs-literal">true</span>

# <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span> Shell Integration <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>
# Auto<span class="hljs-operator">-</span>detect zsh<span class="hljs-operator">/</span>bash<span class="hljs-operator">/</span>fish and inject integration scripts
shell<span class="hljs-operator">-</span>integration <span class="hljs-operator">=</span> detect

# <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span> Performance <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>
# <span class="hljs-operator">~</span>25MB scrollback buffer
scrollback<span class="hljs-operator">-</span>limit <span class="hljs-operator">=</span> <span class="hljs-number">25000000</span>

# <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span> Keybinds <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>
# Tabs (use case: different SSH connections in separate tabs)
keybind <span class="hljs-operator">=</span> cmd<span class="hljs-operator">+</span>t<span class="hljs-operator">=</span>new_tab
keybind <span class="hljs-operator">=</span> cmd<span class="hljs-operator">+</span>shift<span class="hljs-operator">+</span>left<span class="hljs-operator">=</span>previous_tab
keybind <span class="hljs-operator">=</span> cmd<span class="hljs-operator">+</span>shift<span class="hljs-operator">+</span>right<span class="hljs-operator">=</span>next_tab
keybind <span class="hljs-operator">=</span> cmd<span class="hljs-operator">+</span>w<span class="hljs-operator">=</span>close_surface

# Quick Terminal <span class="hljs-keyword">global</span> hotkey
keybind <span class="hljs-operator">=</span> <span class="hljs-keyword">global</span>:ctrl<span class="hljs-operator">+</span>grave_accent<span class="hljs-operator">=</span>toggle_quick_terminal

# Font size
keybind <span class="hljs-operator">=</span> cmd<span class="hljs-operator">+</span>plus<span class="hljs-operator">=</span>increase_font_size:<span class="hljs-number">1</span>
keybind <span class="hljs-operator">=</span> cmd<span class="hljs-operator">+</span>minus<span class="hljs-operator">=</span>decrease_font_size:<span class="hljs-number">1</span>
keybind <span class="hljs-operator">=</span> cmd<span class="hljs-operator">+</span>zero<span class="hljs-operator">=</span>reset_font_size

# Hot reload config file (no restart needed after config changes)
keybind <span class="hljs-operator">=</span> cmd<span class="hljs-operator">+</span>shift<span class="hljs-operator">+</span>comma<span class="hljs-operator">=</span>reload_config
</code></pre><p><strong>zhangchitc's original config:</strong></p><p>See <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://gist.github.com/zhangchitc/7dead7c1b517390e061e07759ed80277">zhangchitc's GitHub Gist</a>.</p><br>]]></content:encoded>
            <author>lostleaf@newsletter.paragraph.com (lostleaf.eth)</author>
            <category>ghostty</category>
            <category>macos</category>
            <category>claudecode</category>
            <category>terminal</category>
            <enclosure url="https://storage.googleapis.com/papyrus_images/3c179d114b5079492305e50bbb2bf06e0f8ede0402c042b1a455c29a25ee6319.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Using asyncio and WebSocket to Retrieve and Record Binance K-Line Market Data]]></title>
            <link>https://paragraph.com/@lostleaf/using-asyncio-and-websocket-to-retrieve-and-record-binance-k-line-market-data</link>
            <guid>2wV1Vq2wHVUCn2pDBB4X</guid>
            <pubDate>Wed, 03 Jul 2024 05:57:44 GMT</pubDate>
            <description><![CDATA[As we know, Binance offers two methods to obtain K-line data: REST API and WebSocket. Among these, WebSocket is the preferred method recommended by Binance for obtaining real-time data. This guide will show you how to use Python asyncio to subscribe to Binance K-line data via WebSocket, and asynchronously record Binance K-line market data as Pandas DataFrame in Parquet format.Connecting to Binance Market Data WebSocketTo get market data via WebSocket, we first need to implement a robust WebSo...]]></description>
            <content:encoded><![CDATA[<p>As we know, Binance offers two methods to obtain K-line data: REST API and WebSocket. Among these, WebSocket is the preferred method recommended by Binance for obtaining real-time data.</p><p>This guide will show you how to use Python asyncio to subscribe to Binance K-line data via WebSocket, and asynchronously record Binance K-line market data as Pandas DataFrame in Parquet format.</p><h2 id="h-connecting-to-binance-market-data-websocket" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Connecting to Binance Market Data WebSocket</h2><p>To get market data via WebSocket, we first need to implement a robust WebSocket client.</p><p>Here, we will use a simplified version of <code>ReconnectingWebsocket</code> from <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/sammchardy/python-binance/blob/master/binance/streams.py">python-binance&apos;s </a><code>streams.py</code>, which can be directly called from <code>ws_basics.py</code>.</p><p>In <code>binance_market_ws.py</code>, we define the functions to generate Binance K-line WebSocket connections as follows:</p><pre data-type="codeBlock" text="from ws_basics import ReconnectingWebsocket

# Spot WS Base URL
SPOT_STREAM_URL = &apos;wss://stream.binance.com:9443/&apos;

# USDT Futures WS Base URL
USDT_FUTURES_FSTREAM_URL = &apos;wss://fstream.binance.com/&apos;

# Coin Futures WS Base URL
COIN_FUTURES_DSTREAM_URL = &apos;wss://dstream.binance.com/&apos;


def get_coin_futures_multi_candlesticks_socket(symbols, time_inteval):
    &quot;&quot;&quot;
    Returns a WebSocket connection for multiple symbols&apos; K-line data for coin-margined futures.
    &quot;&quot;&quot;
    channels = [f&apos;{s.lower()}@kline_{time_inteval}&apos; for s in symbols]
    return ReconnectingWebsocket(
        path=&apos;/&apos;.join(channels),
        url=COIN_FUTURES_DSTREAM_URL,
        prefix=&apos;stream?streams=&apos;,
    )


def get_usdt_futures_multi_candlesticks_socket(symbols, time_inteval):
    &quot;&quot;&quot;
    Returns a WebSocket connection for multiple symbols&apos; K-line data for USDT-margined futures.
    &quot;&quot;&quot;
    channels = [f&apos;{s.lower()}@kline_{time_inteval}&apos; for s in symbols]
    return ReconnectingWebsocket(
        path=&apos;/&apos;.join(channels),
        url=USDT_FUTURES_FSTREAM_URL,
        prefix=&apos;stream?streams=&apos;,
    )

def get_spot_multi_candlesticks_socket(symbols, time_inteval):
    &quot;&quot;&quot;
    Returns a WebSocket connection for multiple symbols&apos; K-line data for spot trading.
    &quot;&quot;&quot;
    channels = [f&apos;{s.lower()}@kline_{time_inteval}&apos; for s in symbols]
    return ReconnectingWebsocket(
        path=&apos;/&apos;.join(channels),
        url=SPOT_STREAM_URL,
        prefix=&apos;stream?streams=&apos;,
    )
"><code><span class="hljs-keyword">from</span> ws_basics <span class="hljs-keyword">import</span> ReconnectingWebsocket

<span class="hljs-comment"># Spot WS Base URL</span>
SPOT_STREAM_URL = <span class="hljs-string">'wss://stream.binance.com:9443/'</span>

<span class="hljs-comment"># USDT Futures WS Base URL</span>
USDT_FUTURES_FSTREAM_URL = <span class="hljs-string">'wss://fstream.binance.com/'</span>

<span class="hljs-comment"># Coin Futures WS Base URL</span>
COIN_FUTURES_DSTREAM_URL = <span class="hljs-string">'wss://dstream.binance.com/'</span>


<span class="hljs-keyword">def</span> <span class="hljs-title function_">get_coin_futures_multi_candlesticks_socket</span>(<span class="hljs-params">symbols, time_inteval</span>):
    <span class="hljs-string">"""
    Returns a WebSocket connection for multiple symbols' K-line data for coin-margined futures.
    """</span>
    channels = [<span class="hljs-string">f'<span class="hljs-subst">{s.lower()}</span>@kline_<span class="hljs-subst">{time_inteval}</span>'</span> <span class="hljs-keyword">for</span> s <span class="hljs-keyword">in</span> symbols]
    <span class="hljs-keyword">return</span> ReconnectingWebsocket(
        path=<span class="hljs-string">'/'</span>.join(channels),
        url=COIN_FUTURES_DSTREAM_URL,
        prefix=<span class="hljs-string">'stream?streams='</span>,
    )


<span class="hljs-keyword">def</span> <span class="hljs-title function_">get_usdt_futures_multi_candlesticks_socket</span>(<span class="hljs-params">symbols, time_inteval</span>):
    <span class="hljs-string">"""
    Returns a WebSocket connection for multiple symbols' K-line data for USDT-margined futures.
    """</span>
    channels = [<span class="hljs-string">f'<span class="hljs-subst">{s.lower()}</span>@kline_<span class="hljs-subst">{time_inteval}</span>'</span> <span class="hljs-keyword">for</span> s <span class="hljs-keyword">in</span> symbols]
    <span class="hljs-keyword">return</span> ReconnectingWebsocket(
        path=<span class="hljs-string">'/'</span>.join(channels),
        url=USDT_FUTURES_FSTREAM_URL,
        prefix=<span class="hljs-string">'stream?streams='</span>,
    )

<span class="hljs-keyword">def</span> <span class="hljs-title function_">get_spot_multi_candlesticks_socket</span>(<span class="hljs-params">symbols, time_inteval</span>):
    <span class="hljs-string">"""
    Returns a WebSocket connection for multiple symbols' K-line data for spot trading.
    """</span>
    channels = [<span class="hljs-string">f'<span class="hljs-subst">{s.lower()}</span>@kline_<span class="hljs-subst">{time_inteval}</span>'</span> <span class="hljs-keyword">for</span> s <span class="hljs-keyword">in</span> symbols]
    <span class="hljs-keyword">return</span> ReconnectingWebsocket(
        path=<span class="hljs-string">'/'</span>.join(channels),
        url=SPOT_STREAM_URL,
        prefix=<span class="hljs-string">'stream?streams='</span>,
    )
</code></pre><p>Using the example code <code>ex1_recv_single.py</code>, we try to connect to the WebSocket and receive BTCUSDT perpetual contract 1-minute K-line data and print it to the screen:</p><pre data-type="codeBlock" text="import asyncio
import logging

from binance_market_ws import get_usdt_futures_multi_candlesticks_socket


async def main():
    socket = get_usdt_futures_multi_candlesticks_socket([&apos;BTCUSDT&apos;], &apos;1m&apos;)
    async with socket as socket_conn:
        while True:
            try:
                res = await socket_conn.recv()
                print(res)
            except asyncio.TimeoutError:
                logging.error(&apos;Recv candle ws timeout&apos;)
                break


if __name__ == &apos;__main__&apos;:
    asyncio.run(main())
"><code><span class="hljs-keyword">import</span> asyncio
<span class="hljs-keyword">import</span> logging

<span class="hljs-keyword">from</span> binance_market_ws <span class="hljs-keyword">import</span> get_usdt_futures_multi_candlesticks_socket


<span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">main</span>():
    socket = get_usdt_futures_multi_candlesticks_socket([<span class="hljs-string">'BTCUSDT'</span>], <span class="hljs-string">'1m'</span>)
    <span class="hljs-keyword">async</span> <span class="hljs-keyword">with</span> socket <span class="hljs-keyword">as</span> socket_conn:
        <span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:
            <span class="hljs-keyword">try</span>:
                res = <span class="hljs-keyword">await</span> socket_conn.recv()
                <span class="hljs-built_in">print</span>(res)
            <span class="hljs-keyword">except</span> asyncio.TimeoutError:
                logging.error(<span class="hljs-string">'Recv candle ws timeout'</span>)
                <span class="hljs-keyword">break</span>


<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">'__main__'</span>:
    asyncio.run(main())
</code></pre><p>Running the above code, we get representative data similar to the following:</p><pre data-type="codeBlock" text="{&apos;stream&apos;: &apos;btcusdt@kline_1m&apos;, &apos;data&apos;: {&apos;e&apos;: &apos;kline&apos;, &apos;E&apos;: 1719765539838, &apos;s&apos;: &apos;BTCUSDT&apos;, &apos;k&apos;: {&apos;t&apos;: 1719765480000, &apos;T&apos;: 1719765539999, &apos;s&apos;: &apos;BTCUSDT&apos;, &apos;i&apos;: &apos;1m&apos;, &apos;f&apos;: 5122041311, &apos;L&apos;: 5122041720, &apos;o&apos;: &apos;61607.90&apos;, &apos;c&apos;: &apos;61623.30&apos;, &apos;h&apos;: &apos;61623.30&apos;, &apos;l&apos;: &apos;61605.30&apos;, &apos;v&apos;: &apos;16.692&apos;, &apos;n&apos;: 410, &apos;x&apos;: False, &apos;q&apos;: &apos;1028411.77850&apos;, &apos;V&apos;: &apos;12.553&apos;, &apos;Q&apos;: &apos;773414.33780&apos;, &apos;B&apos;: &apos;0&apos;}}}
{&apos;stream&apos;: &apos;btcusdt@kline_1m&apos;, &apos;data&apos;: {&apos;e&apos;: &apos;kline&apos;, &apos;E&apos;: 1719765540037, &apos;s&apos;: &apos;BTCUSDT&apos;, &apos;k&apos;: {&apos;t&apos;: 1719765480000, &apos;T&apos;: 1719765539999, &apos;s&apos;: &apos;BTCUSDT&apos;, &apos;i&apos;: &apos;1m&apos;, &apos;f&apos;: 5122041311, &apos;L&apos;: 5122041728, &apos;o&apos;: &apos;61607.90&apos;, &apos;c&apos;: &apos;61624.90&apos;, &apos;h&apos;: &apos;61624.90&apos;, &apos;l&apos;: &apos;61605.30&apos;, &apos;v&apos;: &apos;16.710&apos;, &apos;n&apos;: 418, &apos;x&apos;: True, &apos;q&apos;: &apos;1029521.00470&apos;, &apos;V&apos;: &apos;12.571&apos;, &apos;Q&apos;: &apos;774523.56400&apos;, &apos;B&apos;: &apos;0&apos;}}}
{&apos;stream&apos;: &apos;btcusdt@kline_1m&apos;, &apos;data&apos;: {&apos;e&apos;: &apos;kline&apos;, &apos;E&apos;: 1719765540545, &apos;s&apos;: &apos;BTCUSDT&apos;, &apos;k&apos;: {&apos;t&apos;: 1719765540000, &apos;T&apos;: 1719765599999, &apos;s&apos;: &apos;BTCUSDT&apos;, &apos;i&apos;: &apos;1m&apos;, &apos;f&apos;: 5122041729, &apos;L&apos;: 5122041730, &apos;o&apos;: &apos;61624.90&apos;, &apos;c&apos;: &apos;61625.00&apos;, &apos;h&apos;: &apos;61625.00&apos;, &apos;l&apos;: &apos;61624.90&apos;, &apos;v&apos;: &apos;0.026&apos;, &apos;n&apos;: 2, &apos;x&apos;: False, &apos;q&apos;: &apos;1602.24770&apos;, &apos;V&apos;: &apos;0.003&apos;, &apos;Q&apos;: &apos;184.87500&apos;, &apos;B&apos;: &apos;0&apos;}}}
"><code>{<span class="hljs-string">'stream'</span>: <span class="hljs-string">'btcusdt@kline_1m'</span>, <span class="hljs-string">'data'</span>: {<span class="hljs-string">'e'</span>: <span class="hljs-string">'kline'</span>, <span class="hljs-string">'E'</span>: <span class="hljs-number">1719765539838</span>, <span class="hljs-string">'s'</span>: <span class="hljs-string">'BTCUSDT'</span>, <span class="hljs-string">'k'</span>: {<span class="hljs-string">'t'</span>: <span class="hljs-number">1719765480000</span>, <span class="hljs-string">'T'</span>: <span class="hljs-number">1719765539999</span>, <span class="hljs-string">'s'</span>: <span class="hljs-string">'BTCUSDT'</span>, <span class="hljs-string">'i'</span>: <span class="hljs-string">'1m'</span>, <span class="hljs-string">'f'</span>: <span class="hljs-number">5122041311</span>, <span class="hljs-string">'L'</span>: <span class="hljs-number">5122041720</span>, <span class="hljs-string">'o'</span>: <span class="hljs-string">'61607.90'</span>, <span class="hljs-string">'c'</span>: <span class="hljs-string">'61623.30'</span>, <span class="hljs-string">'h'</span>: <span class="hljs-string">'61623.30'</span>, <span class="hljs-string">'l'</span>: <span class="hljs-string">'61605.30'</span>, <span class="hljs-string">'v'</span>: <span class="hljs-string">'16.692'</span>, <span class="hljs-string">'n'</span>: <span class="hljs-number">410</span>, <span class="hljs-string">'x'</span>: <span class="hljs-literal">False</span>, <span class="hljs-string">'q'</span>: <span class="hljs-string">'1028411.77850'</span>, <span class="hljs-string">'V'</span>: <span class="hljs-string">'12.553'</span>, <span class="hljs-string">'Q'</span>: <span class="hljs-string">'773414.33780'</span>, <span class="hljs-string">'B'</span>: <span class="hljs-string">'0'</span>}}}
{<span class="hljs-string">'stream'</span>: <span class="hljs-string">'btcusdt@kline_1m'</span>, <span class="hljs-string">'data'</span>: {<span class="hljs-string">'e'</span>: <span class="hljs-string">'kline'</span>, <span class="hljs-string">'E'</span>: <span class="hljs-number">1719765540037</span>, <span class="hljs-string">'s'</span>: <span class="hljs-string">'BTCUSDT'</span>, <span class="hljs-string">'k'</span>: {<span class="hljs-string">'t'</span>: <span class="hljs-number">1719765480000</span>, <span class="hljs-string">'T'</span>: <span class="hljs-number">1719765539999</span>, <span class="hljs-string">'s'</span>: <span class="hljs-string">'BTCUSDT'</span>, <span class="hljs-string">'i'</span>: <span class="hljs-string">'1m'</span>, <span class="hljs-string">'f'</span>: <span class="hljs-number">5122041311</span>, <span class="hljs-string">'L'</span>: <span class="hljs-number">5122041728</span>, <span class="hljs-string">'o'</span>: <span class="hljs-string">'61607.90'</span>, <span class="hljs-string">'c'</span>: <span class="hljs-string">'61624.90'</span>, <span class="hljs-string">'h'</span>: <span class="hljs-string">'61624.90'</span>, <span class="hljs-string">'l'</span>: <span class="hljs-string">'61605.30'</span>, <span class="hljs-string">'v'</span>: <span class="hljs-string">'16.710'</span>, <span class="hljs-string">'n'</span>: <span class="hljs-number">418</span>, <span class="hljs-string">'x'</span>: <span class="hljs-literal">True</span>, <span class="hljs-string">'q'</span>: <span class="hljs-string">'1029521.00470'</span>, <span class="hljs-string">'V'</span>: <span class="hljs-string">'12.571'</span>, <span class="hljs-string">'Q'</span>: <span class="hljs-string">'774523.56400'</span>, <span class="hljs-string">'B'</span>: <span class="hljs-string">'0'</span>}}}
{<span class="hljs-string">'stream'</span>: <span class="hljs-string">'btcusdt@kline_1m'</span>, <span class="hljs-string">'data'</span>: {<span class="hljs-string">'e'</span>: <span class="hljs-string">'kline'</span>, <span class="hljs-string">'E'</span>: <span class="hljs-number">1719765540545</span>, <span class="hljs-string">'s'</span>: <span class="hljs-string">'BTCUSDT'</span>, <span class="hljs-string">'k'</span>: {<span class="hljs-string">'t'</span>: <span class="hljs-number">1719765540000</span>, <span class="hljs-string">'T'</span>: <span class="hljs-number">1719765599999</span>, <span class="hljs-string">'s'</span>: <span class="hljs-string">'BTCUSDT'</span>, <span class="hljs-string">'i'</span>: <span class="hljs-string">'1m'</span>, <span class="hljs-string">'f'</span>: <span class="hljs-number">5122041729</span>, <span class="hljs-string">'L'</span>: <span class="hljs-number">5122041730</span>, <span class="hljs-string">'o'</span>: <span class="hljs-string">'61624.90'</span>, <span class="hljs-string">'c'</span>: <span class="hljs-string">'61625.00'</span>, <span class="hljs-string">'h'</span>: <span class="hljs-string">'61625.00'</span>, <span class="hljs-string">'l'</span>: <span class="hljs-string">'61624.90'</span>, <span class="hljs-string">'v'</span>: <span class="hljs-string">'0.026'</span>, <span class="hljs-string">'n'</span>: <span class="hljs-number">2</span>, <span class="hljs-string">'x'</span>: <span class="hljs-literal">False</span>, <span class="hljs-string">'q'</span>: <span class="hljs-string">'1602.24770'</span>, <span class="hljs-string">'V'</span>: <span class="hljs-string">'0.003'</span>, <span class="hljs-string">'Q'</span>: <span class="hljs-string">'184.87500'</span>, <span class="hljs-string">'B'</span>: <span class="hljs-string">'0'</span>}}}
</code></pre><p>Each piece of data is parsed into a Python dictionary, and we need to convert this data dictionary into a DataFrame.</p><h2 id="h-parsing-binance-market-data" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Parsing Binance Market Data</h2><p>According to the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams/Kline-Candlestick-Streams">Binance documentation</a>, we can map the <code>k</code> field in the data dictionary to common K-line DataFrame column names.</p><pre data-type="codeBlock" text="import pandas as pd

def convert_to_dataframe(x, interval_delta):
    &quot;&quot;&quot;
    Parse WS returned data dictionary, return as DataFrame
    &quot;&quot;&quot;
    columns = [
        &apos;candle_begin_time&apos;, &apos;open&apos;, &apos;high&apos;, &apos;low&apos;, &apos;close&apos;, &apos;volume&apos;, &apos;quote_volume&apos;, &apos;trade_num&apos;,
        &apos;taker_buy_base_asset_volume&apos;, &apos;taker_buy_quote_asset_volume&apos;
    ]
    candle_data = [
        pd.to_datetime(int(x[&apos;t&apos;]), unit=&apos;ms&apos;, utc=True),
        float(x[&apos;o&apos;]),
        float(x[&apos;h&apos;]),
        float(x[&apos;l&apos;]),
        float(x[&apos;c&apos;]),
        float(x[&apos;v&apos;]),
        float(x[&apos;q&apos;]),
        float(x[&apos;n&apos;]),
        float(x[&apos;V&apos;]),
        float(x[&apos;Q&apos;])
    ]

    # Use K-line end time as the timestamp
    return pd.DataFrame(data=[candle_data], columns=columns, index=[candle_data[0] + interval_delta])
"><code><span class="hljs-keyword">import</span> pandas <span class="hljs-keyword">as</span> pd

<span class="hljs-keyword">def</span> <span class="hljs-title function_">convert_to_dataframe</span>(<span class="hljs-params">x, interval_delta</span>):
    <span class="hljs-string">"""
    Parse WS returned data dictionary, return as DataFrame
    """</span>
    columns = [
        <span class="hljs-string">'candle_begin_time'</span>, <span class="hljs-string">'open'</span>, <span class="hljs-string">'high'</span>, <span class="hljs-string">'low'</span>, <span class="hljs-string">'close'</span>, <span class="hljs-string">'volume'</span>, <span class="hljs-string">'quote_volume'</span>, <span class="hljs-string">'trade_num'</span>,
        <span class="hljs-string">'taker_buy_base_asset_volume'</span>, <span class="hljs-string">'taker_buy_quote_asset_volume'</span>
    ]
    candle_data = [
        pd.to_datetime(<span class="hljs-built_in">int</span>(x[<span class="hljs-string">'t'</span>]), unit=<span class="hljs-string">'ms'</span>, utc=<span class="hljs-literal">True</span>),
        <span class="hljs-built_in">float</span>(x[<span class="hljs-string">'o'</span>]),
        <span class="hljs-built_in">float</span>(x[<span class="hljs-string">'h'</span>]),
        <span class="hljs-built_in">float</span>(x[<span class="hljs-string">'l'</span>]),
        <span class="hljs-built_in">float</span>(x[<span class="hljs-string">'c'</span>]),
        <span class="hljs-built_in">float</span>(x[<span class="hljs-string">'v'</span>]),
        <span class="hljs-built_in">float</span>(x[<span class="hljs-string">'q'</span>]),
        <span class="hljs-built_in">float</span>(x[<span class="hljs-string">'n'</span>]),
        <span class="hljs-built_in">float</span>(x[<span class="hljs-string">'V'</span>]),
        <span class="hljs-built_in">float</span>(x[<span class="hljs-string">'Q'</span>])
    ]

    <span class="hljs-comment"># Use K-line end time as the timestamp</span>
    <span class="hljs-keyword">return</span> pd.DataFrame(data=[candle_data], columns=columns, index=[candle_data[<span class="hljs-number">0</span>] + interval_delta])
</code></pre><p>For robustness, we need to further adopt defensive programming, strictly checking data validity and determining if the K-line is closed, only accepting closed K-lines.</p><pre data-type="codeBlock" text="def handle_candle_data(res, interval_delta):
    &quot;&quot;&quot;
    Handle WS returned data
    &quot;&quot;&quot;

    # Defensive programming, discard if Binance returns no data field
    if &apos;data&apos; not in res:
        return

    # Extract data field
    data = res[&apos;data&apos;]

    # Defensive programming, discard if data does not contain e field or e field (data type) is not kline or data does not contain k field (K-line data)
    if data.get(&apos;e&apos;, None) != &apos;kline&apos; or &apos;k&apos; not in data:
        return

    # Extract k field, i.e., K-line data
    candle = data[&apos;k&apos;]

    # Determine if K-line is closed, discard if not closed
    is_closed = candle.get(&apos;x&apos;, False)
    if not is_closed:
        return

    # Convert K-line to DataFrame
    df_candle = convert_to_dataframe(candle, interval_delta)
    return df_candle
"><code><span class="hljs-keyword">def</span> <span class="hljs-title function_">handle_candle_data</span>(<span class="hljs-params">res, interval_delta</span>):
    <span class="hljs-string">"""
    Handle WS returned data
    """</span>

    <span class="hljs-comment"># Defensive programming, discard if Binance returns no data field</span>
    <span class="hljs-keyword">if</span> <span class="hljs-string">'data'</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> res:
        <span class="hljs-keyword">return</span>

    <span class="hljs-comment"># Extract data field</span>
    data = res[<span class="hljs-string">'data'</span>]

    <span class="hljs-comment"># Defensive programming, discard if data does not contain e field or e field (data type) is not kline or data does not contain k field (K-line data)</span>
    <span class="hljs-keyword">if</span> data.get(<span class="hljs-string">'e'</span>, <span class="hljs-literal">None</span>) != <span class="hljs-string">'kline'</span> <span class="hljs-keyword">or</span> <span class="hljs-string">'k'</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> data:
        <span class="hljs-keyword">return</span>

    <span class="hljs-comment"># Extract k field, i.e., K-line data</span>
    candle = data[<span class="hljs-string">'k'</span>]

    <span class="hljs-comment"># Determine if K-line is closed, discard if not closed</span>
    is_closed = candle.get(<span class="hljs-string">'x'</span>, <span class="hljs-literal">False</span>)
    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> is_closed:
        <span class="hljs-keyword">return</span>

    <span class="hljs-comment"># Convert K-line to DataFrame</span>
    df_candle = convert_to_dataframe(candle, interval_delta)
    <span class="hljs-keyword">return</span> df_candle
</code></pre><p>Based on the example code <code>ex2_parse_data.py</code>, we attempt to parse the K-line data received in the previous section (K-line data saved as <code>ex2_ws_candle.json</code>).</p><pre data-type="codeBlock" text="import json
import pandas as pd

def main():
    # Load JSON data
    data = json.load(open(&apos;ex2_ws_candle.json&apos;))

    # K-line interval is 1m
    interval_delta = pd.Timedelta(minutes=1)

    # Try to parse each WS data
    for idx, row in enumerate(data, 1):
        row_parsed = handle_candle_data(row, interval_delta)
        if row_parsed is None:
            print(f&apos;Row{idx} is None&apos;)
        else:
            print(f&apos;Row{idx} candlestick\n&apos; + str(row_parsed))


if __name__ == &apos;__main__&apos;:
    main()
"><code><span class="hljs-keyword">import</span> json
<span class="hljs-keyword">import</span> pandas <span class="hljs-keyword">as</span> pd

<span class="hljs-keyword">def</span> <span class="hljs-title function_">main</span>():
    <span class="hljs-comment"># Load JSON data</span>
    data = json.load(<span class="hljs-built_in">open</span>(<span class="hljs-string">'ex2_ws_candle.json'</span>))

    <span class="hljs-comment"># K-line interval is 1m</span>
    interval_delta = pd.Timedelta(minutes=<span class="hljs-number">1</span>)

    <span class="hljs-comment"># Try to parse each WS data</span>
    <span class="hljs-keyword">for</span> idx, row <span class="hljs-keyword">in</span> <span class="hljs-built_in">enumerate</span>(data, <span class="hljs-number">1</span>):
        row_parsed = handle_candle_data(row, interval_delta)
        <span class="hljs-keyword">if</span> row_parsed <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:
            <span class="hljs-built_in">print</span>(<span class="hljs-string">f'Row<span class="hljs-subst">{idx}</span> is None'</span>)
        <span class="hljs-keyword">else</span>:
            <span class="hljs-built_in">print</span>(<span class="hljs-string">f'Row<span class="hljs-subst">{idx}</span> candlestick\n'</span> + <span class="hljs-built_in">str</span>(row_parsed))


<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">'__main__'</span>:
    main()
</code></pre><p>The output is as follows:</p><pre data-type="codeBlock" text="Row1 is None
Row2 candlestick
                                  candle_begin_time     open     high      low    close  volume  quote_volume  trade_num  taker_buy_base_asset_volume  taker_buy_quote_asset_volume
2024-06-30 16:39:00+00:00 2024-06-30 16:38:00+00:00  61607.9  61624.9  61605.3  61624.9   16.71  1.029521e+06      418.0                       12.571                    774523.564
Row3 is None
"><code>Row1 <span class="hljs-keyword">is</span> None
Row2 candlestick
                                  candle_begin_time     open     high      low    close  volume  quote_volume  trade_num  taker_buy_base_asset_volume  taker_buy_quote_asset_volume
<span class="hljs-number">2024</span><span class="hljs-operator">-</span>06<span class="hljs-number">-30</span> <span class="hljs-number">16</span>:<span class="hljs-number">39</span>:00<span class="hljs-operator">+</span>00:00 <span class="hljs-number">2024</span><span class="hljs-operator">-</span>06<span class="hljs-number">-30</span> <span class="hljs-number">16</span>:<span class="hljs-number">38</span>:00<span class="hljs-operator">+</span>00:00  <span class="hljs-number">61607.9</span>  <span class="hljs-number">61624.9</span>  <span class="hljs-number">61605.3</span>  <span class="hljs-number">61624.9</span>   <span class="hljs-number">16.71</span>  <span class="hljs-number">1.029521e+06</span>      <span class="hljs-number">418.0</span>                       <span class="hljs-number">12.571</span>                    <span class="hljs-number">774523.564</span>
Row3 <span class="hljs-keyword">is</span> None
</code></pre><p>The first and third records are discarded because the K-line is not closed, resulting in an output of None.</p><p>The second record is a closed K-line and is parsed into a DataFrame.</p><h2 id="h-single-time-interval-multiple-symbol-websocket-k-line-data-receiver-candlelistener" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Single Time Interval Multiple Symbol WebSocket K-Line Data Receiver <code>CandleListener</code></h2><p>Combining the previous two sections, we can define <code>CandleListener</code> (<code>candle_listener.py</code>).</p><p>The main function is <code>start_listen</code>, which is responsible for establishing the WebSocket connection and receiving K-line data.</p><p>The <code>handle_candle_data</code> function is responsible for parsing the received K-line data, pushing valid and closed K-lines into the message queue (<code>self.que</code>) introduced in the next section.</p><pre data-type="codeBlock" text="import asyncio
import logging
from datetime import datetime, timedelta

import pandas as pd
import pytz

from binance_market_ws import (get_coin_futures_multi_candlesticks_socket, get_spot_multi_candlesticks_socket,
                               get_usdt_futures_multi_candlesticks_socket)


def convert_to_dataframe(x, interval_delta):
    &quot;&quot;&quot;
    Parse the dictionary returned by WS and return a DataFrame
    &quot;&quot;&quot;
    columns = [
        &apos;candle_begin_time&apos;, &apos;open&apos;, &apos;high&apos;, &apos;low&apos;, &apos;close&apos;, &apos;volume&apos;, &apos;quote_volume&apos;, &apos;trade_num&apos;,
        &apos;taker_buy_base_asset_volume&apos;, &apos;taker_buy_quote_asset_volume&apos;
    ]
    candle_data = [
        pd.to_datetime(int(x[&apos;t&apos;]), unit=&apos;ms&apos;, utc=True),
        float(x[&apos;o&apos;]),
        float(x[&apos;h&apos;]),
        float(x[&apos;l&apos;]),
        float(x[&apos;c&apos;]),
        float(x[&apos;v&apos;]),
        float(x[&apos;q&apos;]),
        float(x[&apos;n&apos;]),
        float(x[&apos;V&apos;]),
        float(x[&apos;Q&apos;])
    ]

    # Use the K-line end time as the timestamp
    return pd.DataFrame(data=[candle_data], columns=columns, index=[candle_data[0] + interval_delta])


class CandleListener:

    # Mapping of trade types to ws functions
    TRADE_TYPE_MAP = {
        &apos;usdt_futures&apos;: get_usdt_futures_multi_candlesticks_socket,
        &apos;coin_futures&apos;: get_coin_futures_multi_candlesticks_socket,
        &apos;spot&apos;: get_spot_multi_candlesticks_socket
    }

    def __init__(self, type_, symbols, time_interval, que):
        # Trade type
        self.trade_type = type_
        # Trading symbols
        self.symbols = set(symbols)
        # K-line period
        self.time_interval = time_interval
        self.interval_delta = convert_interval_to_timedelta(time_interval)
        # Message queue
        self.que: asyncio.Queue = que
        # Reconnection flag
        self.req_reconnect = False

    async def start_listen(self):
        &quot;&quot;&quot;
        Main function for WS listening
        &quot;&quot;&quot;

        if not self.symbols:
            return
        
        socket_func = self.TRADE_TYPE_MAP[self.trade_type]
        while True:
            # Create WS
            socket = socket_func(self.symbols, self.time_interval)
            async with socket as socket_conn:
                # After the WS connection is successful, receive and parse the data
                while True:
                    if self.req_reconnect:
                        # Reconnect if reconnection is required
                        self.req_reconnect = False
                        break
                    try:
                        res = await socket_conn.recv()
                        self.handle_candle_data(res)
                    except asyncio.TimeoutError: 
                        # Reconnect if no data is received for long (default 60 seconds)
                        # Normally K-line is pushed every 1-2 seconds                        
                        logging.error(&apos;Recv candle ws timeout, reconnecting&apos;)
                        break

    def handle_candle_data(self, res):
        &quot;&quot;&quot;
        Handle data returned by WS
        &quot;&quot;&quot;

        # Defensive programming, discard if Binance returns an error without the data field
        if &apos;data&apos; not in res:
            return

        # Extract the data field
        data = res[&apos;data&apos;]

        # Defensive programming, discard if the data does not contain the e field or the e field (data type) is not kline or the data does not contain the k field (K-line data)
        if data.get(&apos;e&apos;, None) != &apos;kline&apos; or &apos;k&apos; not in data:
            return

        # Extract the k field, which is the K-line data
        candle = data[&apos;k&apos;]

        # Check if the K-line is closed, discard if not closed
        is_closed = candle.get(&apos;x&apos;, False)
        if not is_closed:
            return

        # Convert the K-line to a DataFrame
        df_candle = convert_to_dataframe(candle, self.interval_delta)

        # Put the K-line DataFrame into the communication queue
        self.que.put_nowait({
            &apos;type&apos;: &apos;candle_data&apos;,
            &apos;data&apos;: df_candle,
            &apos;closed&apos;: is_closed,
            &apos;run_time&apos;: df_candle.index[0],
            &apos;symbol&apos;: data[&apos;s&apos;],
            &apos;time_interval&apos;: self.time_interval,
            &apos;trade_type&apos;: self.trade_type,
            &apos;recv_time&apos;: now_time()
        })

    def add_symbols(self, *symbols):
        for symbol in symbols:
            self.symbols.add(symbol)

    def remove_symbols(self, *symbols):
        for symbol in symbols:
            if symbol in self.symbols:
                self.symbols.remove(symbol)

    def reconnect(self):
        self.req_reconnect = True
"><code><span class="hljs-keyword">import</span> asyncio
<span class="hljs-keyword">import</span> logging
<span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime, timedelta

<span class="hljs-keyword">import</span> pandas <span class="hljs-keyword">as</span> pd
<span class="hljs-keyword">import</span> pytz

<span class="hljs-keyword">from</span> binance_market_ws <span class="hljs-keyword">import</span> (get_coin_futures_multi_candlesticks_socket, get_spot_multi_candlesticks_socket,
                               get_usdt_futures_multi_candlesticks_socket)


<span class="hljs-keyword">def</span> <span class="hljs-title function_">convert_to_dataframe</span>(<span class="hljs-params">x, interval_delta</span>):
    <span class="hljs-string">"""
    Parse the dictionary returned by WS and return a DataFrame
    """</span>
    columns = [
        <span class="hljs-string">'candle_begin_time'</span>, <span class="hljs-string">'open'</span>, <span class="hljs-string">'high'</span>, <span class="hljs-string">'low'</span>, <span class="hljs-string">'close'</span>, <span class="hljs-string">'volume'</span>, <span class="hljs-string">'quote_volume'</span>, <span class="hljs-string">'trade_num'</span>,
        <span class="hljs-string">'taker_buy_base_asset_volume'</span>, <span class="hljs-string">'taker_buy_quote_asset_volume'</span>
    ]
    candle_data = [
        pd.to_datetime(<span class="hljs-built_in">int</span>(x[<span class="hljs-string">'t'</span>]), unit=<span class="hljs-string">'ms'</span>, utc=<span class="hljs-literal">True</span>),
        <span class="hljs-built_in">float</span>(x[<span class="hljs-string">'o'</span>]),
        <span class="hljs-built_in">float</span>(x[<span class="hljs-string">'h'</span>]),
        <span class="hljs-built_in">float</span>(x[<span class="hljs-string">'l'</span>]),
        <span class="hljs-built_in">float</span>(x[<span class="hljs-string">'c'</span>]),
        <span class="hljs-built_in">float</span>(x[<span class="hljs-string">'v'</span>]),
        <span class="hljs-built_in">float</span>(x[<span class="hljs-string">'q'</span>]),
        <span class="hljs-built_in">float</span>(x[<span class="hljs-string">'n'</span>]),
        <span class="hljs-built_in">float</span>(x[<span class="hljs-string">'V'</span>]),
        <span class="hljs-built_in">float</span>(x[<span class="hljs-string">'Q'</span>])
    ]

    <span class="hljs-comment"># Use the K-line end time as the timestamp</span>
    <span class="hljs-keyword">return</span> pd.DataFrame(data=[candle_data], columns=columns, index=[candle_data[<span class="hljs-number">0</span>] + interval_delta])


<span class="hljs-keyword">class</span> <span class="hljs-title class_">CandleListener</span>:

    <span class="hljs-comment"># Mapping of trade types to ws functions</span>
    TRADE_TYPE_MAP = {
        <span class="hljs-string">'usdt_futures'</span>: get_usdt_futures_multi_candlesticks_socket,
        <span class="hljs-string">'coin_futures'</span>: get_coin_futures_multi_candlesticks_socket,
        <span class="hljs-string">'spot'</span>: get_spot_multi_candlesticks_socket
    }

    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, type_, symbols, time_interval, que</span>):
        <span class="hljs-comment"># Trade type</span>
        self.trade_type = type_
        <span class="hljs-comment"># Trading symbols</span>
        self.symbols = <span class="hljs-built_in">set</span>(symbols)
        <span class="hljs-comment"># K-line period</span>
        self.time_interval = time_interval
        self.interval_delta = convert_interval_to_timedelta(time_interval)
        <span class="hljs-comment"># Message queue</span>
        self.que: asyncio.Queue = que
        <span class="hljs-comment"># Reconnection flag</span>
        self.req_reconnect = <span class="hljs-literal">False</span>

    <span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">start_listen</span>(<span class="hljs-params">self</span>):
        <span class="hljs-string">"""
        Main function for WS listening
        """</span>

        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> self.symbols:
            <span class="hljs-keyword">return</span>
        
        socket_func = self.TRADE_TYPE_MAP[self.trade_type]
        <span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:
            <span class="hljs-comment"># Create WS</span>
            socket = socket_func(self.symbols, self.time_interval)
            <span class="hljs-keyword">async</span> <span class="hljs-keyword">with</span> socket <span class="hljs-keyword">as</span> socket_conn:
                <span class="hljs-comment"># After the WS connection is successful, receive and parse the data</span>
                <span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:
                    <span class="hljs-keyword">if</span> self.req_reconnect:
                        <span class="hljs-comment"># Reconnect if reconnection is required</span>
                        self.req_reconnect = <span class="hljs-literal">False</span>
                        <span class="hljs-keyword">break</span>
                    <span class="hljs-keyword">try</span>:
                        res = <span class="hljs-keyword">await</span> socket_conn.recv()
                        self.handle_candle_data(res)
                    <span class="hljs-keyword">except</span> asyncio.TimeoutError: 
                        <span class="hljs-comment"># Reconnect if no data is received for long (default 60 seconds)</span>
                        <span class="hljs-comment"># Normally K-line is pushed every 1-2 seconds                        </span>
                        logging.error(<span class="hljs-string">'Recv candle ws timeout, reconnecting'</span>)
                        <span class="hljs-keyword">break</span>

    <span class="hljs-keyword">def</span> <span class="hljs-title function_">handle_candle_data</span>(<span class="hljs-params">self, res</span>):
        <span class="hljs-string">"""
        Handle data returned by WS
        """</span>

        <span class="hljs-comment"># Defensive programming, discard if Binance returns an error without the data field</span>
        <span class="hljs-keyword">if</span> <span class="hljs-string">'data'</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> res:
            <span class="hljs-keyword">return</span>

        <span class="hljs-comment"># Extract the data field</span>
        data = res[<span class="hljs-string">'data'</span>]

        <span class="hljs-comment"># Defensive programming, discard if the data does not contain the e field or the e field (data type) is not kline or the data does not contain the k field (K-line data)</span>
        <span class="hljs-keyword">if</span> data.get(<span class="hljs-string">'e'</span>, <span class="hljs-literal">None</span>) != <span class="hljs-string">'kline'</span> <span class="hljs-keyword">or</span> <span class="hljs-string">'k'</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> data:
            <span class="hljs-keyword">return</span>

        <span class="hljs-comment"># Extract the k field, which is the K-line data</span>
        candle = data[<span class="hljs-string">'k'</span>]

        <span class="hljs-comment"># Check if the K-line is closed, discard if not closed</span>
        is_closed = candle.get(<span class="hljs-string">'x'</span>, <span class="hljs-literal">False</span>)
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> is_closed:
            <span class="hljs-keyword">return</span>

        <span class="hljs-comment"># Convert the K-line to a DataFrame</span>
        df_candle = convert_to_dataframe(candle, self.interval_delta)

        <span class="hljs-comment"># Put the K-line DataFrame into the communication queue</span>
        self.que.put_nowait({
            <span class="hljs-string">'type'</span>: <span class="hljs-string">'candle_data'</span>,
            <span class="hljs-string">'data'</span>: df_candle,
            <span class="hljs-string">'closed'</span>: is_closed,
            <span class="hljs-string">'run_time'</span>: df_candle.index[<span class="hljs-number">0</span>],
            <span class="hljs-string">'symbol'</span>: data[<span class="hljs-string">'s'</span>],
            <span class="hljs-string">'time_interval'</span>: self.time_interval,
            <span class="hljs-string">'trade_type'</span>: self.trade_type,
            <span class="hljs-string">'recv_time'</span>: now_time()
        })

    <span class="hljs-keyword">def</span> <span class="hljs-title function_">add_symbols</span>(<span class="hljs-params">self, *symbols</span>):
        <span class="hljs-keyword">for</span> symbol <span class="hljs-keyword">in</span> symbols:
            self.symbols.add(symbol)

    <span class="hljs-keyword">def</span> <span class="hljs-title function_">remove_symbols</span>(<span class="hljs-params">self, *symbols</span>):
        <span class="hljs-keyword">for</span> symbol <span class="hljs-keyword">in</span> symbols:
            <span class="hljs-keyword">if</span> symbol <span class="hljs-keyword">in</span> self.symbols:
                self.symbols.remove(symbol)

    <span class="hljs-keyword">def</span> <span class="hljs-title function_">reconnect</span>(<span class="hljs-params">self</span>):
        self.req_reconnect = <span class="hljs-literal">True</span>
</code></pre><h2 id="h-recording-binance-k-line-market-data" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Recording Binance K-Line Market Data</h2><p>In this section, we asynchronously receive K-line data for spot, USDT-margined contracts, and coin-margined contracts through multiple WebSocket connections and store the K-line data DataFrame on the hard disk in Parquet format.</p><p>To achieve this, we first review the producer-consumer architecture: The producer-consumer architecture is a common software design pattern in concurrent scenarios, where producers provide data, consumers process data, and data is typically passed between producers and consumers through a message queue.</p><p>This pattern decouples data production from data processing. In our business scenario, using multiple producers and a single consumer ensures the correctness of hard disk writes.</p><p>In the example code <code>ex3_record_multi.py</code>, we define three producers, all of which are instances of <code>CandleListener</code>:</p><ul><li><p><code>listener_usdt_perp_1m</code>: Receives 1-minute K-line data for the BTCUSDT and ETHUSDT USDT-margined contracts.</p></li><li><p><code>listener_coin_perp_3m</code>: Receives 3-minute K-line data for the BTCUSD_PERP and ETHUSD_PERP coin-margined contracts.</p></li><li><p><code>listener_spot_1m</code>: Receives 1-minute K-line data for the BTCUSDT and BNBUSDT spot pairs.</p></li></ul><p>A consumer is defined to update the K-line data.</p><pre data-type="codeBlock" text="def update_candle_data(df_new: pd.DataFrame, symbol, time_interval, trade_type):
    &quot;&quot;&quot;
    Writes the received K-line data DataFrame to the hard disk in Parquet format.
    &quot;&quot;&quot;
    output_path = f&apos;{trade_type}_{symbol}_{time_interval}.pqt&apos;

    if not os.path.exists(output_path):
        df_new.to_parquet(output_path, compression=&apos;zstd&apos;)
        return

    df = pd.read_parquet(output_path)
    df = pd.concat([df, df_new])
    df.sort_index()
    df.drop_duplicates(&apos;candle_begin_time&apos;)
    df.to_parquet(output_path)


async def dispatcher(main_que: asyncio.Queue):
    &quot;&quot;&quot;
    Consumer that processes the received K-line data.
    &quot;&quot;&quot;
    while True:
        # Get data from the main queue
        req = await main_que.get()
        run_time = req[&apos;run_time&apos;]
        req_type = req[&apos;type&apos;]

        # Call the appropriate processing function based on the data type
        if req_type == &apos;candle_data&apos;:  # K-line data update
            symbol = req[&apos;symbol&apos;]
            time_interval = req[&apos;time_interval&apos;]
            trade_type = req[&apos;trade_type&apos;]
            update_candle_data(req[&apos;data&apos;], symbol, time_interval, trade_type)
            logging.info(&apos;Record %s %s-%s at %s&apos;, trade_type, symbol, time_interval, run_time)
        else:
            logging.warning(&apos;Unknown request %s %s&apos;, req_type, run_time)
"><code>def update_candle_data(df_new: pd.DataFrame, symbol, time_interval, trade_type):
    <span class="hljs-string">""</span><span class="hljs-string">"
    Writes the received K-line data DataFrame to the hard disk in Parquet format.
    "</span><span class="hljs-string">""</span>
    output_path = f<span class="hljs-string">'{trade_type}_{symbol}_{time_interval}.pqt'</span>

    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> os.path.exists(output_path):
        df_new.to_parquet(output_path, compression=<span class="hljs-string">'zstd'</span>)
        <span class="hljs-keyword">return</span>

    df = pd.read_parquet(output_path)
    df = pd.concat([df, df_new])
    df.sort_index()
    df.drop_duplicates(<span class="hljs-string">'candle_begin_time'</span>)
    df.to_parquet(output_path)


async def dispatcher(main_que: asyncio.Queue):
    <span class="hljs-string">""</span><span class="hljs-string">"
    Consumer that processes the received K-line data.
    "</span><span class="hljs-string">""</span>
    <span class="hljs-keyword">while</span> True:
        <span class="hljs-comment"># Get data from the main queue</span>
        req = await main_que.get()
        run_time = re<span class="hljs-string">q['run_time']</span>
        req_type = re<span class="hljs-string">q['type']</span>

        <span class="hljs-comment"># Call the appropriate processing function based on the data type</span>
        <span class="hljs-keyword">if</span> req_type == <span class="hljs-string">'candle_data'</span>:  <span class="hljs-comment"># K-line data update</span>
            symbol = re<span class="hljs-string">q['symbol']</span>
            time_interval = re<span class="hljs-string">q['time_interval']</span>
            trade_type = re<span class="hljs-string">q['trade_type']</span>
            update_candle_data(re<span class="hljs-string">q['data']</span>, symbol, time_interval, trade_type)
            logging.info(<span class="hljs-string">'Record %s %s-%s at %s'</span>, trade_type, symbol, time_interval, run_time)
        <span class="hljs-keyword">else</span>:
            logging.warning(<span class="hljs-string">'Unknown request %s %s'</span>, req_type, run_time)
</code></pre><p>The core calling code is as follows:</p><pre data-type="codeBlock" text="# ex3_record_multi.py

async def main():
    logging.info(&apos;Start recording candlestick data&apos;)
    # Main queue
    main_que = asyncio.Queue()

    # Producers
    listener_usdt_perp_1m = CandleListener(&apos;usdt_futures&apos;, [&apos;BTCUSDT&apos;, &apos;ETHUSDT&apos;], &apos;1m&apos;, main_que)
    listener_coin_perp_3m = CandleListener(&apos;coin_futures&apos;, [&apos;BTCUSD_PERP&apos;, &apos;ETHUSD_PERP&apos;], &apos;3m&apos;, main_que)
    listener_spot_1m = CandleListener(&apos;spot&apos;, [&apos;BTCUSDT&apos;, &apos;BNBUSDT&apos;], &apos;1m&apos;, main_que)

    # Consumer
    dispatcher_task = dispatcher(main_que)

    await asyncio.gather(listener_usdt_perp_1m.start_listen(), listener_coin_perp_3m.start_listen(),
                         listener_spot_1m.start_listen(), dispatcher_task)
"><code># ex3_record_multi.py

async def main():
    logging.info(<span class="hljs-string">'Start recording candlestick data'</span>)
    # Main queue
    main_que <span class="hljs-operator">=</span> asyncio.Queue()

    # Producers
    listener_usdt_perp_1m <span class="hljs-operator">=</span> CandleListener(<span class="hljs-string">'usdt_futures'</span>, [<span class="hljs-string">'BTCUSDT'</span>, <span class="hljs-string">'ETHUSDT'</span>], <span class="hljs-string">'1m'</span>, main_que)
    listener_coin_perp_3m <span class="hljs-operator">=</span> CandleListener(<span class="hljs-string">'coin_futures'</span>, [<span class="hljs-string">'BTCUSD_PERP'</span>, <span class="hljs-string">'ETHUSD_PERP'</span>], <span class="hljs-string">'3m'</span>, main_que)
    listener_spot_1m <span class="hljs-operator">=</span> CandleListener(<span class="hljs-string">'spot'</span>, [<span class="hljs-string">'BTCUSDT'</span>, <span class="hljs-string">'BNBUSDT'</span>], <span class="hljs-string">'1m'</span>, main_que)

    # Consumer
    dispatcher_task <span class="hljs-operator">=</span> dispatcher(main_que)

    await asyncio.gather(listener_usdt_perp_1m.start_listen(), listener_coin_perp_3m.start_listen(),
                         listener_spot_1m.start_listen(), dispatcher_task)
</code></pre><p>The runtime output is as follows:</p><pre data-type="codeBlock" text="20240630 22:59:36 (INFO) - Start record candlestick data
20240630 23:00:00 (INFO) - Record usdt_futures ETHUSDT-1m at 2024-06-30 15:00:00+00:00
20240630 23:00:00 (INFO) - Record spot BNBUSDT-1m at 2024-06-30 15:00:00+00:00
20240630 23:00:00 (INFO) - Record spot BTCUSDT-1m at 2024-06-30 15:00:00+00:00
20240630 23:00:00 (INFO) - Record usdt_futures BTCUSDT-1m at 2024-06-30 15:00:00+00:00
20240630 23:00:01 (INFO) - Record coin_futures ETHUSD_PERP-3m at 2024-06-30 15:00:00+00:00
20240630 23:00:02 (INFO) - Record coin_futures BTCUSD_PERP-3m at 2024-06-30 15:00:00+00:00
20240630 23:01:00 (INFO) - Record spot BNBUSDT-1m at 2024-06-30 15:01:00+00:00
20240630 23:01:00 (INFO) - Record spot BTCUSDT-1m at 2024-06-30 15:01:00+00:00
"><code><span class="hljs-number">20240630</span> <span class="hljs-number">22</span>:<span class="hljs-number">59</span>:<span class="hljs-number">36</span> (INFO) <span class="hljs-operator">-</span> Start record candlestick data
<span class="hljs-number">20240630</span> <span class="hljs-number">23</span>:00:00 (INFO) <span class="hljs-operator">-</span> Record usdt_futures ETHUSDT<span class="hljs-operator">-</span>1m at <span class="hljs-number">2024</span><span class="hljs-operator">-</span>06<span class="hljs-number">-30</span> <span class="hljs-number">15</span>:00:00<span class="hljs-operator">+</span>00:00
<span class="hljs-number">20240630</span> <span class="hljs-number">23</span>:00:00 (INFO) <span class="hljs-operator">-</span> Record spot BNBUSDT<span class="hljs-operator">-</span>1m at <span class="hljs-number">2024</span><span class="hljs-operator">-</span>06<span class="hljs-number">-30</span> <span class="hljs-number">15</span>:00:00<span class="hljs-operator">+</span>00:00
<span class="hljs-number">20240630</span> <span class="hljs-number">23</span>:00:00 (INFO) <span class="hljs-operator">-</span> Record spot BTCUSDT<span class="hljs-operator">-</span>1m at <span class="hljs-number">2024</span><span class="hljs-operator">-</span>06<span class="hljs-number">-30</span> <span class="hljs-number">15</span>:00:00<span class="hljs-operator">+</span>00:00
<span class="hljs-number">20240630</span> <span class="hljs-number">23</span>:00:00 (INFO) <span class="hljs-operator">-</span> Record usdt_futures BTCUSDT<span class="hljs-operator">-</span>1m at <span class="hljs-number">2024</span><span class="hljs-operator">-</span>06<span class="hljs-number">-30</span> <span class="hljs-number">15</span>:00:00<span class="hljs-operator">+</span>00:00
<span class="hljs-number">20240630</span> <span class="hljs-number">23</span>:00:01 (INFO) <span class="hljs-operator">-</span> Record coin_futures ETHUSD_PERP<span class="hljs-operator">-</span>3m at <span class="hljs-number">2024</span><span class="hljs-operator">-</span>06<span class="hljs-number">-30</span> <span class="hljs-number">15</span>:00:00<span class="hljs-operator">+</span>00:00
<span class="hljs-number">20240630</span> <span class="hljs-number">23</span>:00:02 (INFO) <span class="hljs-operator">-</span> Record coin_futures BTCUSD_PERP<span class="hljs-operator">-</span>3m at <span class="hljs-number">2024</span><span class="hljs-operator">-</span>06<span class="hljs-number">-30</span> <span class="hljs-number">15</span>:00:00<span class="hljs-operator">+</span>00:00
<span class="hljs-number">20240630</span> <span class="hljs-number">23</span>:01:00 (INFO) <span class="hljs-operator">-</span> Record spot BNBUSDT<span class="hljs-operator">-</span>1m at <span class="hljs-number">2024</span><span class="hljs-operator">-</span>06<span class="hljs-number">-30</span> <span class="hljs-number">15</span>:01:00<span class="hljs-operator">+</span>00:00
<span class="hljs-number">20240630</span> <span class="hljs-number">23</span>:01:00 (INFO) <span class="hljs-operator">-</span> Record spot BTCUSDT<span class="hljs-operator">-</span>1m at <span class="hljs-number">2024</span><span class="hljs-operator">-</span>06<span class="hljs-number">-30</span> <span class="hljs-number">15</span>:01:00<span class="hljs-operator">+</span>00:00
</code></pre><p>The Parquet files written to the hard disk are as follows:</p><pre data-type="codeBlock" text="-rw-rw-r-- 1 admin admin 8.8K Jun 30 23:09 coin_futures_BTCUSD_PERP_3m.pqt
-rw-rw-r-- 1 admin admin 8.8K Jun 30 23:09 coin_futures_ETHUSD_PERP_3m.pqt
-rw-rw-r-- 1 admin admin 9.3K Jun 30 23:10 spot_BNBUSDT_1m.pqt
-rw-rw-r-- 1 admin admin 9.3K Jun 30 23:10 spot_BTCUSDT_1m.pqt
-rw-rw-r-- 1 admin admin 9.4K Jun 30 23:10 usdt_futures_BTCUSDT_1m.pqt
-rw-rw-r-- 1 admin admin 9.4K Jun 30 23:10 usdt_futures_ETHUSDT_1m.pqt
"><code><span class="hljs-operator">-</span>rw<span class="hljs-operator">-</span>rw<span class="hljs-operator">-</span>r<span class="hljs-operator">-</span><span class="hljs-operator">-</span> <span class="hljs-number">1</span> admin admin <span class="hljs-number">8</span>.8K Jun <span class="hljs-number">30</span> <span class="hljs-number">23</span>:09 coin_futures_BTCUSD_PERP_3m.pqt
<span class="hljs-operator">-</span>rw<span class="hljs-operator">-</span>rw<span class="hljs-operator">-</span>r<span class="hljs-operator">-</span><span class="hljs-operator">-</span> <span class="hljs-number">1</span> admin admin <span class="hljs-number">8</span>.8K Jun <span class="hljs-number">30</span> <span class="hljs-number">23</span>:09 coin_futures_ETHUSD_PERP_3m.pqt
<span class="hljs-operator">-</span>rw<span class="hljs-operator">-</span>rw<span class="hljs-operator">-</span>r<span class="hljs-operator">-</span><span class="hljs-operator">-</span> <span class="hljs-number">1</span> admin admin <span class="hljs-number">9</span>.3K Jun <span class="hljs-number">30</span> <span class="hljs-number">23</span>:<span class="hljs-number">10</span> spot_BNBUSDT_1m.pqt
<span class="hljs-operator">-</span>rw<span class="hljs-operator">-</span>rw<span class="hljs-operator">-</span>r<span class="hljs-operator">-</span><span class="hljs-operator">-</span> <span class="hljs-number">1</span> admin admin <span class="hljs-number">9</span>.3K Jun <span class="hljs-number">30</span> <span class="hljs-number">23</span>:<span class="hljs-number">10</span> spot_BTCUSDT_1m.pqt
<span class="hljs-operator">-</span>rw<span class="hljs-operator">-</span>rw<span class="hljs-operator">-</span>r<span class="hljs-operator">-</span><span class="hljs-operator">-</span> <span class="hljs-number">1</span> admin admin <span class="hljs-number">9</span>.4K Jun <span class="hljs-number">30</span> <span class="hljs-number">23</span>:<span class="hljs-number">10</span> usdt_futures_BTCUSDT_1m.pqt
<span class="hljs-operator">-</span>rw<span class="hljs-operator">-</span>rw<span class="hljs-operator">-</span>r<span class="hljs-operator">-</span><span class="hljs-operator">-</span> <span class="hljs-number">1</span> admin admin <span class="hljs-number">9</span>.4K Jun <span class="hljs-number">30</span> <span class="hljs-number">23</span>:<span class="hljs-number">10</span> usdt_futures_ETHUSDT_1m.pqt
</code></pre><p>The recorded data examples are as follows:</p><pre data-type="codeBlock" text="In [2]: pd.read_parquet(&apos;usdt_futures_BTCUSDT_1m.pqt&apos;)
Out[2]:
                                  candle_begin_time     open     high      low    close   volume  quote_volume  trade_num  taker_buy_base_asset_volume  taker_buy_quote_asset_volume
2024-06-30 15:00:00+00:00 2024-06-30 14:59:00+00:00  61580.2  61586.1  61580.2  61581.7   18.203  1.121016e+06      340.0                        6.931                  4.268353e+05
2024-06-30 15:01:00+00:00 2024-06-30 15:00:00+00:00  61581.8  61612.8  61581.7  61612.7   79.385  4.890301e+06     1015.0                       62.865                  3.872662e+06
......
2024-06-30 15:10:00+00:00 2024-06-30 15:09:00+00:00  61643.5  61643.5  61633.5  61636.9   35.319  2.176951e+06      530.0                       11.421                  7.039525e+05

In [3]: pd.read_parquet(&apos;coin_futures_ETHUSD_PERP_3m.pqt&apos;)
Out[3]:
                                  candle_begin_time     open     high      low    close   volume  quote_volume  trade_num  taker_buy_base_asset_volume  taker_buy_quote_asset_volume
2024-06-30 15:00:00+00:00 2024-06-30 14:57:00+00:00  3386.98  3387.00  3386.48  3386.98  54646.0    161.348842      259.0                      31434.0                     92.810019
2024-06-30 15:03:00+00:00 2024-06-30 15:00:00+00:00  3386.98  3389.36  3386.98  3389.35  29754.0     87.806150      275.0                      27826.0                     82.116929
2024-06-30 15:06:00+00:00 2024-06-30 15:03:00+00:00  3389.36  3389.79  3387.27  3388.39  39127.0    115.465249      363.0                       9255.0                     27.315708
2024-06-30 15:09:00+00:00 2024-06-30 15:06:00+00:00  3388.39  3390.59  3388.39  3390.22  15443.0     45.562138      220.0                       8133.0                     23.994293
"><code>In [<span class="hljs-number">2</span>]: pd.read_parquet(<span class="hljs-string">'usdt_futures_BTCUSDT_1m.pqt'</span>)
Out[<span class="hljs-number">2</span>]:
                                  candle_begin_time     open     high      low    close   volume  quote_volume  trade_num  taker_buy_base_asset_volume  taker_buy_quote_asset_volume
<span class="hljs-number">2024</span><span class="hljs-operator">-</span>06<span class="hljs-number">-30</span> <span class="hljs-number">15</span>:00:00<span class="hljs-operator">+</span>00:00 <span class="hljs-number">2024</span><span class="hljs-operator">-</span>06<span class="hljs-number">-30</span> <span class="hljs-number">14</span>:<span class="hljs-number">59</span>:00<span class="hljs-operator">+</span>00:00  <span class="hljs-number">61580.2</span>  <span class="hljs-number">61586.1</span>  <span class="hljs-number">61580.2</span>  <span class="hljs-number">61581.7</span>   <span class="hljs-number">18.203</span>  <span class="hljs-number">1.121016e+06</span>      <span class="hljs-number">340.0</span>                        <span class="hljs-number">6.931</span>                  <span class="hljs-number">4.268353e+05</span>
<span class="hljs-number">2024</span><span class="hljs-operator">-</span>06<span class="hljs-number">-30</span> <span class="hljs-number">15</span>:01:00<span class="hljs-operator">+</span>00:00 <span class="hljs-number">2024</span><span class="hljs-operator">-</span>06<span class="hljs-number">-30</span> <span class="hljs-number">15</span>:00:00<span class="hljs-operator">+</span>00:00  <span class="hljs-number">61581.8</span>  <span class="hljs-number">61612.8</span>  <span class="hljs-number">61581.7</span>  <span class="hljs-number">61612.7</span>   <span class="hljs-number">79.385</span>  <span class="hljs-number">4.890301e+06</span>     <span class="hljs-number">1015.0</span>                       <span class="hljs-number">62.865</span>                  <span class="hljs-number">3.872662e+06</span>
......
<span class="hljs-number">2024</span><span class="hljs-operator">-</span>06<span class="hljs-number">-30</span> <span class="hljs-number">15</span>:<span class="hljs-number">10</span>:00<span class="hljs-operator">+</span>00:00 <span class="hljs-number">2024</span><span class="hljs-operator">-</span>06<span class="hljs-number">-30</span> <span class="hljs-number">15</span>:09:00<span class="hljs-operator">+</span>00:00  <span class="hljs-number">61643.5</span>  <span class="hljs-number">61643.5</span>  <span class="hljs-number">61633.5</span>  <span class="hljs-number">61636.9</span>   <span class="hljs-number">35.319</span>  <span class="hljs-number">2.176951e+06</span>      <span class="hljs-number">530.0</span>                       <span class="hljs-number">11.421</span>                  <span class="hljs-number">7.039525e+05</span>

In [<span class="hljs-number">3</span>]: pd.read_parquet(<span class="hljs-string">'coin_futures_ETHUSD_PERP_3m.pqt'</span>)
Out[<span class="hljs-number">3</span>]:
                                  candle_begin_time     open     high      low    close   volume  quote_volume  trade_num  taker_buy_base_asset_volume  taker_buy_quote_asset_volume
<span class="hljs-number">2024</span><span class="hljs-operator">-</span>06<span class="hljs-number">-30</span> <span class="hljs-number">15</span>:00:00<span class="hljs-operator">+</span>00:00 <span class="hljs-number">2024</span><span class="hljs-operator">-</span>06<span class="hljs-number">-30</span> <span class="hljs-number">14</span>:<span class="hljs-number">57</span>:00<span class="hljs-operator">+</span>00:00  <span class="hljs-number">3386.98</span>  <span class="hljs-number">3387.00</span>  <span class="hljs-number">3386.48</span>  <span class="hljs-number">3386.98</span>  <span class="hljs-number">54646.0</span>    <span class="hljs-number">161.348842</span>      <span class="hljs-number">259.0</span>                      <span class="hljs-number">31434.0</span>                     <span class="hljs-number">92.810019</span>
<span class="hljs-number">2024</span><span class="hljs-operator">-</span>06<span class="hljs-number">-30</span> <span class="hljs-number">15</span>:03:00<span class="hljs-operator">+</span>00:00 <span class="hljs-number">2024</span><span class="hljs-operator">-</span>06<span class="hljs-number">-30</span> <span class="hljs-number">15</span>:00:00<span class="hljs-operator">+</span>00:00  <span class="hljs-number">3386.98</span>  <span class="hljs-number">3389.36</span>  <span class="hljs-number">3386.98</span>  <span class="hljs-number">3389.35</span>  <span class="hljs-number">29754.0</span>     <span class="hljs-number">87.806150</span>      <span class="hljs-number">275.0</span>                      <span class="hljs-number">27826.0</span>                     <span class="hljs-number">82.116929</span>
<span class="hljs-number">2024</span><span class="hljs-operator">-</span>06<span class="hljs-number">-30</span> <span class="hljs-number">15</span>:06:00<span class="hljs-operator">+</span>00:00 <span class="hljs-number">2024</span><span class="hljs-operator">-</span>06<span class="hljs-number">-30</span> <span class="hljs-number">15</span>:03:00<span class="hljs-operator">+</span>00:00  <span class="hljs-number">3389.36</span>  <span class="hljs-number">3389.79</span>  <span class="hljs-number">3387.27</span>  <span class="hljs-number">3388.39</span>  <span class="hljs-number">39127.0</span>    <span class="hljs-number">115.465249</span>      <span class="hljs-number">363.0</span>                       <span class="hljs-number">9255.0</span>                     <span class="hljs-number">27.315708</span>
<span class="hljs-number">2024</span><span class="hljs-operator">-</span>06<span class="hljs-number">-30</span> <span class="hljs-number">15</span>:09:00<span class="hljs-operator">+</span>00:00 <span class="hljs-number">2024</span><span class="hljs-operator">-</span>06<span class="hljs-number">-30</span> <span class="hljs-number">15</span>:06:00<span class="hljs-operator">+</span>00:00  <span class="hljs-number">3388.39</span>  <span class="hljs-number">3390.59</span>  <span class="hljs-number">3388.39</span>  <span class="hljs-number">3390.22</span>  <span class="hljs-number">15443.0</span>     <span class="hljs-number">45.562138</span>      <span class="hljs-number">220.0</span>                       <span class="hljs-number">8133.0</span>                     <span class="hljs-number">23.994293</span>
</code></pre><p>At this point, we have essentially implemented a simple asynchronous Binance K-line data client.</p><p>However, creating a robust Binance real-time data client is not simple; more logic needs to be added to ensure data integrity and accuracy.</p><p>Stay tuned for subsequent technical reports.</p>]]></content:encoded>
            <author>lostleaf@newsletter.paragraph.com (lostleaf.eth)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/48a8bd39859e00115263495b6c1b5611f50e574d88b9f0b728adba5c6eb9dac1.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Notes: How the Economic Machine Works]]></title>
            <link>https://paragraph.com/@lostleaf/notes-how-the-economic-machine-works</link>
            <guid>w9feKXCeGYG0BnHcI54o</guid>
            <pubDate>Wed, 10 Apr 2024 04:25:15 GMT</pubDate>
            <description><![CDATA[This article summarizes Ray Dalio’s video How The Economic Machine Works The economy operates like a simple machine, yet many people are unaware or disagree with this perspective, leading to unnecessary economic losses. Here is an analysis framework that may not perfectly align with traditional economics but is highly useful:The economy appears complex but functions in a straightforward and mechanical way.It is composed of a few simple parts and countless simple transactions, driven by human ...]]></description>
            <content:encoded><![CDATA[<p>This article summarizes Ray Dalio’s video <em>How The Economic Machine Works</em></p><div data-type="youtube" videoId="PHe0bXAIuk0">
      <div class="youtube-player" data-id="PHe0bXAIuk0" style="background-image: url('https://i.ytimg.com/vi/PHe0bXAIuk0/hqdefault.jpg'); background-size: cover; background-position: center">
        <a href="https://www.youtube.com/watch?v=PHe0bXAIuk0">
          <img src="{{DOMAIN}}/editor/youtube/play.png" class="play"/>
        </a>
      </div></div><p>The economy operates like a simple machine, yet many people are unaware or disagree with this perspective, leading to unnecessary economic losses.</p><p>Here is an analysis framework that may not perfectly align with traditional economics but is highly useful:</p><ul><li><p>The economy appears complex but functions in a straightforward and mechanical way.</p></li><li><p>It is composed of a few simple parts and countless simple transactions, driven by human nature.</p></li><li><p>Three main economic forces:</p><ol><li><p>Productivity growth</p></li><li><p>Short-term debt cycle</p></li><li><p>Long-term debt cycle</p></li></ol></li></ul><h2 id="h-transactions" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Transactions</h2><ul><li><p>An exchange between a buyer and a seller is called a transaction, which occurs constantly.</p></li><li><p>The economy is the sum total of numerous transactions.</p></li><li><p>In each transaction: <strong>Buyers</strong> pay money or credit to obtain goods, services, or financial assets from <strong>sellers</strong>.</p><ul><li><p>The sum of money and credit paid by the buyer is the total spending, which is the driving force of the economy, i.e., <code>Total Spending = Money + Credit</code>.</p></li><li><p>Dividing the total spending by the seller&apos;s product sales volume gives the price, i.e., <code>Price = Total Spending / Sales Volume</code>.</p></li></ul></li><li><p>Transactions are the fundamental components of the economic machine; all economic cycles and forces are caused by transactions. Thus, understanding transactions is to understand the economy.</p></li></ul><h2 id="h-markets" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Markets</h2><ul><li><p>A market consists of sellers and buyers trading the same type of goods, such as wheat markets, car markets, stock markets, etc.</p></li><li><p>The economy is made up of all transactions within all markets. Adding up all the markets&apos; total spending and sales volumes gives us all the information needed to understand the economy.</p></li><li><p>Individuals, companies, banks, and governments all engage in transactions.</p></li><li><p>The government is the largest seller and buyer, consisting of the central government and the central bank:</p><ul><li><p>Central government: Responsible for taxation and spending.</p></li><li><p>Central bank: Unlike other buyers and sellers, the central bank controls the amount of money and credit in the economy by influencing interest rates and issuing currency, playing a crucial role in the circulation of credit.</p></li></ul></li></ul><h2 id="h-credit" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Credit</h2><ul><li><p>Credit is the most important and most volatile part of the economy.</p></li><li><p>Participants in credit include borrowers and lenders:</p><ul><li><p>Lenders: Desire to earn interest on their money by lending it to borrowers.</p></li><li><p>Borrowers: Wish to purchase something they currently cannot afford and borrow money from lenders, repaying the principal and interest at a later date.</p></li></ul></li><li><p>The impact of interest rates on borrowing: High interest rates mean more interest to pay, reducing borrowing; low interest rates encourage borrowing due to lower interest expenses.</p></li><li><p>As long as borrowers can assure repayment and lenders trust their promise, credit is created; thus, any two people can create credit out of thin air.</p></li><li><p>Debt comes with the creation of credit; it is an asset to the lender and a liability to the borrower. When the loan is repaid, these assets and liabilities disappear simultaneously.</p></li><li><p>A borrower&apos;s creditworthiness includes two aspects:</p><ul><li><p>A higher ratio of income to debt indicates good repayment capacity.</p></li><li><p>Sufficient collateral that can be used to repay the debt if unable to pay back in cash.</p></li></ul></li><li><p>The significance of credit: When borrowers obtain credit, they can increase spending, which in turn increases someone else&apos;s income; the increase in income can lead to more borrowing and credit, causing the economy to rise, but also leading to <strong>economic cycles</strong>.</p></li></ul><h2 id="h-cycles" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Cycles</h2><ul><li><p>Over the long term, productivity improvements lead to income growth, but productivity does not fluctuate sharply and is not a significant driver of short-term economic fluctuations.</p></li><li><p>In the short term, credit is most crucial because borrowing leads to consumption exceeding output, and repayment causes consumption to fall below output.</p></li><li><p>Fluctuations in debt levels create two major cycles:</p><ul><li><p>Short-term debt cycle: Lasts approximately 5-8 years.</p></li><li><p>Long-term debt cycle: Lasts approximately 75-100 years.</p></li></ul></li><li><p>The rise and fall of the economy depend mainly on the total amount of credit. Without credit, the economy would grow solely in line with productivity.</p></li><li><p>Due to the existence of credit, the economy experiences cyclical fluctuations, determined by human nature.</p></li><li><p>A cycle that applies both to individuals and the overall economy:</p><ul><li><p>Currently, consumption exceeds income through borrowing.</p></li><li><p>In the future, consumption must be less than spending to repay debts.</p></li></ul></li></ul><h2 id="h-the-difference-between-money-and-credit" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">The Difference Between Money and Credit</h2><ul><li><p>Transactions completed with money are settled immediately.</p></li><li><p>Transactions completed with credit, such as buying on credit, create liabilities and assets, and are only concluded once the debt is repaid, with liabilities and assets disappearing simultaneously.</p></li><li><p>In reality, most money is actually credit. For example, in the United States, the total credit is approximately $50 trillion, while the total money supply is about $3 trillion.</p></li><li><p>In a credit system, increasing credit can boost income growth in the short term, but not necessarily in the long term.</p></li><li><p>Credit is not inherently bad, but it does cause cyclical changes in the economy:</p><ul><li><p>Bad credit: Credit leads to consumption that exceeds the ability to repay.</p></li><li><p>Good credit: Efficiently allocates resources and generates income that can repay debts.</p></li></ul></li><li><p>Borrowing creates cycles that rise and fall.</p></li></ul><h2 id="h-short-term-debt-cycle" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Short-term Debt Cycle</h2><ul><li><p>The first phase: Expansion occurs as economic activity increases; during this stage, credit is created out of thin air, spending increases, but total sales volume does not keep up with the increase in spending, leading to rising prices and inflation.</p></li><li><p>The second phase: The central bank, not wanting high inflation, raises interest rates, making fewer people borrow and increasing repayments, which reduces spending and income. Prices drop, causing deflation and economic recession.</p></li><li><p>The next cycle: The central bank lowers interest rates, leading to another economic expansion.</p></li></ul><p>Thus, in the short-term debt cycle, the economy moves mechanically with interest rate changes: low interest rates lead to expansion, high interest rates lead to contraction.</p><h2 id="h-long-term-debt-cycle" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Long-term Debt Cycle</h2><p>The short-term debt cycles repeat, but after each cycle, economic growth and debt exceed the previous cycle (driven by human nature), forming the <strong>long-term debt cycle</strong>.</p><ul><li><p>Prosperity period:</p><ul><li><p>During this period, incomes rise, and asset prices increase simultaneously, allowing more borrowing and consumption, creating a cycle. However, economic bubbles also form.</p></li><li><p>If debt growth matches the growth rate of income and asset prices, the debt burden remains manageable, but this situation cannot last forever.</p></li></ul></li><li><p>Peak of long-term debt: As the debt burden increases and the cost of debt grows faster than income, people are forced to cut spending, further reducing income and increasing debt costs, forming a vicious cycle, reaching the peak of long-term debt, and reversing the cycle (e.g., the 2008 subprime mortgage crisis, the 1929 Great Depression).</p></li><li><p>Deleveraging period: People cut spending, income falls, credit disappears, asset prices drop, banks face runs, creating a negative spiral. As interest rates are already low, the central bank cannot reverse the situation by lowering rates, and the entire economy loses credibility.</p></li><li><p>Deleveraging can occur in four ways, some leading to deflation, others to inflation. Policymakers need to carefully <strong>balance</strong> the two to deleverage harmoniously:</p><ol><li><p>Cut spending:</p><ul><li><p>During deleveraging, individuals, companies, and governments reduce spending to repay debts, leading to decreased income and an increased debt burden, causing deflation.</p></li><li><p>Companies cut costs, raising unemployment rates.</p></li></ul></li><li><p>Reduce debt:</p><ul><li><p>With falling incomes and rising unemployment rates, borrowers cannot repay loans, banks face runs, and individuals, companies, and banks default, leading to severe economic contraction—depression. The main characteristic is that much of the wealth supported by credit vanishes due to defaults.</p></li><li><p>Some lenders may allow debt restructuring to prevent total loss from defaults, including reducing repayments, extending terms, lowering interest rates, etc. These measures also reduce debt but cause asset prices to fall, resulting in deflation.</p></li></ul></li><li><p>Wealth redistribution:</p><ul><li><p>With falling incomes and rising unemployment, government tax revenues decrease, while governments need to increase spending on stimulus programs and relief efforts, leading to increased fiscal deficits. Governments must finance from the wealthy.</p></li><li><p>Governments might need to tax the wealthy to transfer wealth to the poor to stimulate consumption and the economy, but income disparity leads to social tensions and increased conflict.</p></li><li><p>Similar tensions exist between debtor and creditor nations. In the 1930s, such tensions led to World War II and the Great Depression in the United States.</p></li></ul></li><li><p>Print money:</p><ul><li><p>The central bank can print money to compensate for the lack of credit, stimulating the economy, but this can lead to inflation. Hyperinflation like that of 1920s Germany must be avoided.</p></li><li><p>The central bank can use newly printed money to purchase financial assets and government bonds, as was done during the Great Depression and the 2008 crisis in the US. This helps raise asset prices and bolsters the credit of those with financial assets.</p></li><li><p>Central bank purchases of government bonds help the government finance, distribute relief funds, and implement stimulus plans. Government debt may rise, but the total debt burden decreases.</p></li></ul></li></ol></li><li><p>Reflation: When income levels exceed debt, spending increases, and the economy grows again, entering the reflation phase.</p></li><li><p>The lost decade: Depressions and reflation typically last about ten years, hence the term &quot;the lost decade.&quot;</p></li></ul><h2 id="h-conclusion" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Conclusion</h2><p>Combining productivity growth, the short-term debt cycle, and the long-term debt cycle provides a useful model.</p><p>Although the real economy is much more complex than this model, it helps us clearly understand past and present situations and anticipate potential future developments.</p><h2 id="h-three-rules-of-thumb" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Three Rules of Thumb</h2><ol><li><p>Do not let debt growth exceed income growth.</p></li><li><p>Do not let income growth exceed productivity growth.</p></li><li><p>Strive to increase productivity by all means possible.</p></li></ol>]]></content:encoded>
            <author>lostleaf@newsletter.paragraph.com (lostleaf.eth)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/dd592c6848bfa35e65dde3005aa89d11a1e606dcbb42d500c738a37e6145ce70.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[AROON Technical Indicator Implementation and Optimization]]></title>
            <link>https://paragraph.com/@lostleaf/aroon-technical-indicator-implementation-and-optimization</link>
            <guid>x3cii8OIvjl5Uu0zIaQb</guid>
            <pubDate>Wed, 10 Apr 2024 02:49:00 GMT</pubDate>
            <description><![CDATA[AROON Technical Indicator Implementation and Optimization: Accelerating the rolling argmax Operator by 1500x using Numba and Monotonic Queue AROON is a common technical indicator, whose definition can be found at Investopedia. The challenge in implementing this indicator lies in the rolling argmax operator, for which pandas does not provide an official implementation. A naive implementation based on Series results in considerably slow computations. Optimizing the computation of this indicator...]]></description>
            <content:encoded><![CDATA[<p><strong>AROON Technical Indicator Implementation and Optimization: Accelerating the rolling argmax Operator by 1500x using Numba and Monotonic Queue</strong></p><p>AROON is a common technical indicator, whose definition can be found at <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.investopedia.com/terms/a/aroon.asp#toc-formulas-for-the-aroon-indicator">Investopedia</a>.</p><p>The challenge in implementing this indicator lies in the rolling argmax operator, for which pandas does not provide an official implementation. A naive implementation based on Series results in considerably slow computations.</p><p>Optimizing the computation of this indicator presents an interesting but not easy problem.</p><p>Code reference is available at <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/lostleaf/articles/blob/master/20221013numba_aroon/optimize_aroon.ipynb">Github</a>.</p><h2 id="h-data-and-parameters" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Data and Parameters</h2><p>First, we generate a HLC candlestick DataFrame of length 50,000 through a random walk to simulate the trend of an arbitrary commodity.</p><p>We set the lookback window <code>n=200</code>:</p><pre data-type="codeBlock" text="length = 50000
n = 200

np.random.seed(2022)
cl = 10000 + np.cumsum(np.random.normal(scale=20, size=length))
hi = cl + np.random.rand() * 100
lo = cl - np.random.rand() * 100

df = pd.DataFrame({&apos;high&apos;: hi, &apos;low&apos;: lo, &apos;close&apos;: cl})

print(df.head()[[&apos;high&apos;, &apos;low&apos;, &apos;close&apos;]].to_markdown(), &apos;\n&apos;)

print(df.tail()[[&apos;high&apos;, &apos;low&apos;, &apos;close&apos;]].to_markdown(), &apos;\n&apos;)

print(df.describe().to_markdown())
"><code>length <span class="hljs-operator">=</span> <span class="hljs-number">50000</span>
n <span class="hljs-operator">=</span> <span class="hljs-number">200</span>

np.random.seed(<span class="hljs-number">2022</span>)
cl <span class="hljs-operator">=</span> <span class="hljs-number">10000</span> <span class="hljs-operator">+</span> np.cumsum(np.random.normal(scale<span class="hljs-operator">=</span><span class="hljs-number">20</span>, size<span class="hljs-operator">=</span>length))
hi <span class="hljs-operator">=</span> cl <span class="hljs-operator">+</span> np.random.rand() <span class="hljs-operator">*</span> <span class="hljs-number">100</span>
lo <span class="hljs-operator">=</span> cl <span class="hljs-operator">-</span> np.random.rand() <span class="hljs-operator">*</span> <span class="hljs-number">100</span>

df <span class="hljs-operator">=</span> pd.DataFrame({<span class="hljs-string">'high'</span>: hi, <span class="hljs-string">'low'</span>: lo, <span class="hljs-string">'close'</span>: cl})

print(df.head()[[<span class="hljs-string">'high'</span>, <span class="hljs-string">'low'</span>, <span class="hljs-string">'close'</span>]].to_markdown(), <span class="hljs-string">'\n'</span>)

print(df.tail()[[<span class="hljs-string">'high'</span>, <span class="hljs-string">'low'</span>, <span class="hljs-string">'close'</span>]].to_markdown(), <span class="hljs-string">'\n'</span>)

print(df.describe().to_markdown())
</code></pre><p>Samples and statistics of the generated data:</p><pre data-type="codeBlock" text="|      |    high |     low |   close |
| ---: | ------: | ------: | ------: |
|    0 | 10017.9 | 9965.82 | 9999.99 |
|    1 | 10012.4 | 9960.32 | 9994.49 |
|    2 | 10009.6 | 9957.53 | 9991.71 |
|    3 | 10049.3 | 9997.23 | 10031.4 |
|    4 |   10055 | 10002.9 |   10037 |

|       |    high |     low |   close |
| ----: | ------: | ------: | ------: |
| 49995 | 13187.1 |   13135 | 13169.2 |
| 49996 | 13209.7 | 13157.6 | 13191.8 |
| 49997 | 13223.1 |   13171 | 13205.2 |
| 49998 | 13221.6 | 13169.5 | 13203.7 |
| 49999 | 13242.1 |   13190 | 13224.2 |

|       |    high |     low |   close |
| :---- | ------: | ------: | ------: |
| count |   50000 |   50000 |   50000 |
| mean  | 11801.7 | 11749.6 | 11783.7 |
| std   | 943.887 | 943.887 | 943.887 |
| min   | 9543.01 | 9490.91 | 9525.09 |
| 25%   | 11007.4 | 10955.3 | 10989.5 |
| 50%   | 11986.2 | 11934.1 | 11968.3 |
| 75%   | 12524.3 | 12472.2 | 12506.4 |
| max   | 14159.4 | 14107.3 | 14141.5 |
"><code><span class="hljs-operator">|</span>      <span class="hljs-operator">|</span>    high <span class="hljs-operator">|</span>     low <span class="hljs-operator">|</span>   close <span class="hljs-operator">|</span>
<span class="hljs-operator">|</span> <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>: <span class="hljs-operator">|</span> <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>: <span class="hljs-operator">|</span> <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>: <span class="hljs-operator">|</span> <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>: <span class="hljs-operator">|</span>
<span class="hljs-operator">|</span>    <span class="hljs-number">0</span> <span class="hljs-operator">|</span> <span class="hljs-number">10017.9</span> <span class="hljs-operator">|</span> <span class="hljs-number">9965.82</span> <span class="hljs-operator">|</span> <span class="hljs-number">9999.99</span> <span class="hljs-operator">|</span>
<span class="hljs-operator">|</span>    <span class="hljs-number">1</span> <span class="hljs-operator">|</span> <span class="hljs-number">10012.4</span> <span class="hljs-operator">|</span> <span class="hljs-number">9960.32</span> <span class="hljs-operator">|</span> <span class="hljs-number">9994.49</span> <span class="hljs-operator">|</span>
<span class="hljs-operator">|</span>    <span class="hljs-number">2</span> <span class="hljs-operator">|</span> <span class="hljs-number">10009.6</span> <span class="hljs-operator">|</span> <span class="hljs-number">9957.53</span> <span class="hljs-operator">|</span> <span class="hljs-number">9991.71</span> <span class="hljs-operator">|</span>
<span class="hljs-operator">|</span>    <span class="hljs-number">3</span> <span class="hljs-operator">|</span> <span class="hljs-number">10049.3</span> <span class="hljs-operator">|</span> <span class="hljs-number">9997.23</span> <span class="hljs-operator">|</span> <span class="hljs-number">10031.4</span> <span class="hljs-operator">|</span>
<span class="hljs-operator">|</span>    <span class="hljs-number">4</span> <span class="hljs-operator">|</span>   <span class="hljs-number">10055</span> <span class="hljs-operator">|</span> <span class="hljs-number">10002.9</span> <span class="hljs-operator">|</span>   <span class="hljs-number">10037</span> <span class="hljs-operator">|</span>

<span class="hljs-operator">|</span>       <span class="hljs-operator">|</span>    high <span class="hljs-operator">|</span>     low <span class="hljs-operator">|</span>   close <span class="hljs-operator">|</span>
<span class="hljs-operator">|</span> <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>: <span class="hljs-operator">|</span> <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>: <span class="hljs-operator">|</span> <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>: <span class="hljs-operator">|</span> <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>: <span class="hljs-operator">|</span>
<span class="hljs-operator">|</span> <span class="hljs-number">49995</span> <span class="hljs-operator">|</span> <span class="hljs-number">13187.1</span> <span class="hljs-operator">|</span>   <span class="hljs-number">13135</span> <span class="hljs-operator">|</span> <span class="hljs-number">13169.2</span> <span class="hljs-operator">|</span>
<span class="hljs-operator">|</span> <span class="hljs-number">49996</span> <span class="hljs-operator">|</span> <span class="hljs-number">13209.7</span> <span class="hljs-operator">|</span> <span class="hljs-number">13157.6</span> <span class="hljs-operator">|</span> <span class="hljs-number">13191.8</span> <span class="hljs-operator">|</span>
<span class="hljs-operator">|</span> <span class="hljs-number">49997</span> <span class="hljs-operator">|</span> <span class="hljs-number">13223.1</span> <span class="hljs-operator">|</span>   <span class="hljs-number">13171</span> <span class="hljs-operator">|</span> <span class="hljs-number">13205.2</span> <span class="hljs-operator">|</span>
<span class="hljs-operator">|</span> <span class="hljs-number">49998</span> <span class="hljs-operator">|</span> <span class="hljs-number">13221.6</span> <span class="hljs-operator">|</span> <span class="hljs-number">13169.5</span> <span class="hljs-operator">|</span> <span class="hljs-number">13203.7</span> <span class="hljs-operator">|</span>
<span class="hljs-operator">|</span> <span class="hljs-number">49999</span> <span class="hljs-operator">|</span> <span class="hljs-number">13242.1</span> <span class="hljs-operator">|</span>   <span class="hljs-number">13190</span> <span class="hljs-operator">|</span> <span class="hljs-number">13224.2</span> <span class="hljs-operator">|</span>

<span class="hljs-operator">|</span>       <span class="hljs-operator">|</span>    high <span class="hljs-operator">|</span>     low <span class="hljs-operator">|</span>   close <span class="hljs-operator">|</span>
<span class="hljs-operator">|</span> :<span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span> <span class="hljs-operator">|</span> <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>: <span class="hljs-operator">|</span> <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>: <span class="hljs-operator">|</span> <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>: <span class="hljs-operator">|</span>
<span class="hljs-operator">|</span> count <span class="hljs-operator">|</span>   <span class="hljs-number">50000</span> <span class="hljs-operator">|</span>   <span class="hljs-number">50000</span> <span class="hljs-operator">|</span>   <span class="hljs-number">50000</span> <span class="hljs-operator">|</span>
<span class="hljs-operator">|</span> mean  <span class="hljs-operator">|</span> <span class="hljs-number">11801.7</span> <span class="hljs-operator">|</span> <span class="hljs-number">11749.6</span> <span class="hljs-operator">|</span> <span class="hljs-number">11783.7</span> <span class="hljs-operator">|</span>
<span class="hljs-operator">|</span> std   <span class="hljs-operator">|</span> <span class="hljs-number">943.887</span> <span class="hljs-operator">|</span> <span class="hljs-number">943.887</span> <span class="hljs-operator">|</span> <span class="hljs-number">943.887</span> <span class="hljs-operator">|</span>
<span class="hljs-operator">|</span> min   <span class="hljs-operator">|</span> <span class="hljs-number">9543.01</span> <span class="hljs-operator">|</span> <span class="hljs-number">9490.91</span> <span class="hljs-operator">|</span> <span class="hljs-number">9525.09</span> <span class="hljs-operator">|</span>
<span class="hljs-operator">|</span> <span class="hljs-number">25</span><span class="hljs-operator">%</span>   <span class="hljs-operator">|</span> <span class="hljs-number">11007.4</span> <span class="hljs-operator">|</span> <span class="hljs-number">10955.3</span> <span class="hljs-operator">|</span> <span class="hljs-number">10989.5</span> <span class="hljs-operator">|</span>
<span class="hljs-operator">|</span> <span class="hljs-number">50</span><span class="hljs-operator">%</span>   <span class="hljs-operator">|</span> <span class="hljs-number">11986.2</span> <span class="hljs-operator">|</span> <span class="hljs-number">11934.1</span> <span class="hljs-operator">|</span> <span class="hljs-number">11968.3</span> <span class="hljs-operator">|</span>
<span class="hljs-operator">|</span> <span class="hljs-number">75</span><span class="hljs-operator">%</span>   <span class="hljs-operator">|</span> <span class="hljs-number">12524.3</span> <span class="hljs-operator">|</span> <span class="hljs-number">12472.2</span> <span class="hljs-operator">|</span> <span class="hljs-number">12506.4</span> <span class="hljs-operator">|</span>
<span class="hljs-operator">|</span> max   <span class="hljs-operator">|</span> <span class="hljs-number">14159.4</span> <span class="hljs-operator">|</span> <span class="hljs-number">14107.3</span> <span class="hljs-operator">|</span> <span class="hljs-number">14141.5</span> <span class="hljs-operator">|</span>
</code></pre><h2 id="h-naive-implementation-based-on-pandas-series" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Naive Implementation Based on Pandas Series</h2><p>Initially, we implement a naive version of the Aroon indicator using Series as a baseline.</p><pre data-type="codeBlock" text="def aroon_naive(df, n):
    # index of maximum value in the rolling window
    high_len = df[&apos;high&apos;].rolling(n, min_periods=1).apply(lambda x: pd.Series(x).idxmax())

    high_len = df.index - high_len
    aroon_up = 100 * (n - high_len) / n

    low_len = df[&apos;low&apos;].rolling(n, min_periods=1).apply(lambda x: pd.Series(x).idxmin())
    low_len = df.index - low_len
    aroon_down = 100 * (n - low_len) / n

    return aroon_up, aroon_down

%time up_naive, down_naive = aroon_naive(df, n)
"><code>def aroon_naive(df, n):
    # index of maximum value in the rolling window
    high_len <span class="hljs-operator">=</span> df[<span class="hljs-string">'high'</span>].rolling(n, min_periods<span class="hljs-operator">=</span><span class="hljs-number">1</span>).apply(lambda x: pd.Series(x).idxmax())

    high_len <span class="hljs-operator">=</span> df.index <span class="hljs-operator">-</span> high_len
    aroon_up <span class="hljs-operator">=</span> <span class="hljs-number">100</span> <span class="hljs-operator">*</span> (n <span class="hljs-operator">-</span> high_len) <span class="hljs-operator">/</span> n

    low_len <span class="hljs-operator">=</span> df[<span class="hljs-string">'low'</span>].rolling(n, min_periods<span class="hljs-operator">=</span><span class="hljs-number">1</span>).apply(lambda x: pd.Series(x).idxmin())
    low_len <span class="hljs-operator">=</span> df.index <span class="hljs-operator">-</span> low_len
    aroon_down <span class="hljs-operator">=</span> <span class="hljs-number">100</span> <span class="hljs-operator">*</span> (n <span class="hljs-operator">-</span> low_len) <span class="hljs-operator">/</span> n

    <span class="hljs-keyword">return</span> aroon_up, aroon_down

<span class="hljs-operator">%</span>time up_naive, down_naive <span class="hljs-operator">=</span> aroon_naive(df, n)
</code></pre><p>This version utilizes DataFrame.rolling and creates a new Series in each lambda function to compute the rolling argmax.</p><p>Due to the high cost of creating Series and suboptimal complexity, where complexity = <code>O(N_candles * N_window)</code>, this code is exceedingly slow.</p><p>Test results:</p><pre data-type="codeBlock" text="CPU times: user 2.82 s, sys: 134 ms, total: 2.95 s
Wall time: 2.83 s
"><code><span class="hljs-attr">CPU times:</span> <span class="hljs-string">user</span> <span class="hljs-number">2.82</span> <span class="hljs-string">s,</span> <span class="hljs-attr">sys:</span> <span class="hljs-number">134</span> <span class="hljs-string">ms,</span> <span class="hljs-attr">total:</span> <span class="hljs-number">2.95</span> <span class="hljs-string">s</span>
<span class="hljs-attr">Wall time:</span> <span class="hljs-number">2.83</span> <span class="hljs-string">s</span>
</code></pre><h2 id="h-testing-function" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Testing Function</h2><p>We verify the correctness of each implementation using the following function.</p><p>This function will print out how many <code>nan</code> values are in the the tested signal, how many non-<code>nan</code> values are correct or incorrect, and the accuracy rate.</p><pre data-type="codeBlock" text="def check_signal(sig_original, sig_check):
    print(&apos;Num of nan:&apos;, sig_check.isna().sum())
    mask = sig_original.notnull() &amp; sig_check.notnull()
    n_eq = ((sig_original[mask] - sig_check[mask]).abs() &lt; 1e-8).sum()
    l = mask.sum()
    print(f&apos;Num of equal: {n_eq}, Num of not equal: {l - n_eq}, Ratio good: {n_eq / l * 100.0}%&apos;)
"><code>def check_signal(sig_original, sig_check):
    print(<span class="hljs-string">'Num of nan:'</span>, sig_check.isna().sum())
    mask <span class="hljs-operator">=</span> sig_original.notnull() <span class="hljs-operator">&#x26;</span> sig_check.notnull()
    n_eq <span class="hljs-operator">=</span> ((sig_original[mask] <span class="hljs-operator">-</span> sig_check[mask]).abs() <span class="hljs-operator">&#x3C;</span> <span class="hljs-number">1e-8</span>).sum()
    l <span class="hljs-operator">=</span> mask.sum()
    print(f<span class="hljs-string">'Num of equal: {n_eq}, Num of not equal: {l - n_eq}, Ratio good: {n_eq / l * 100.0}%'</span>)
</code></pre><h2 id="h-numpy-implementation" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Numpy Implementation</h2><p>A simple optimization method is to use numpy functions, avoiding the repeated creation of Series and thus improving efficiency to some extent.</p><pre data-type="codeBlock" text="def high_len_cal(x):
    return (np.maximum.accumulate(x) == x.max()).sum()
  
def low_len_cal(x):
    return (np.minimum.accumulate(x) == x.min()).sum()

def aroon_numpy(df, n):
    high_len = df[&apos;high&apos;].rolling(n).apply(high_len_cal, raw=True, engine=&apos;cython&apos;) - 1
    aroon_up = 100 * (n - high_len) / n

    low_len = df[&apos;low&apos;].rolling(n).apply(low_len_cal, raw=True, engine=&apos;cython&apos;) - 1
    aroon_down = 100 * (n - low_len) / n
    return aroon_up, aroon_down

%time up_numpy, down_numpy = aroon_numpy(df, n)
print(&apos;Check up&apos;)
check_signal(up_naive, up_numpy)

print(&apos;Check down&apos;)
check_signal(down_naive, down_numpy)
"><code>def high_len_cal(x):
    <span class="hljs-keyword">return</span> (np.maximum.accumulate(x) <span class="hljs-operator">=</span><span class="hljs-operator">=</span> x.<span class="hljs-built_in">max</span>()).sum()
  
def low_len_cal(x):
    <span class="hljs-keyword">return</span> (np.minimum.accumulate(x) <span class="hljs-operator">=</span><span class="hljs-operator">=</span> x.<span class="hljs-built_in">min</span>()).sum()

def aroon_numpy(df, n):
    high_len <span class="hljs-operator">=</span> df[<span class="hljs-string">'high'</span>].rolling(n).apply(high_len_cal, raw<span class="hljs-operator">=</span>True, engine<span class="hljs-operator">=</span><span class="hljs-string">'cython'</span>) <span class="hljs-operator">-</span> <span class="hljs-number">1</span>
    aroon_up <span class="hljs-operator">=</span> <span class="hljs-number">100</span> <span class="hljs-operator">*</span> (n <span class="hljs-operator">-</span> high_len) <span class="hljs-operator">/</span> n

    low_len <span class="hljs-operator">=</span> df[<span class="hljs-string">'low'</span>].rolling(n).apply(low_len_cal, raw<span class="hljs-operator">=</span>True, engine<span class="hljs-operator">=</span><span class="hljs-string">'cython'</span>) <span class="hljs-operator">-</span> <span class="hljs-number">1</span>
    aroon_down <span class="hljs-operator">=</span> <span class="hljs-number">100</span> <span class="hljs-operator">*</span> (n <span class="hljs-operator">-</span> low_len) <span class="hljs-operator">/</span> n
    <span class="hljs-keyword">return</span> aroon_up, aroon_down

<span class="hljs-operator">%</span>time up_numpy, down_numpy <span class="hljs-operator">=</span> aroon_numpy(df, n)
print(<span class="hljs-string">'Check up'</span>)
check_signal(up_naive, up_numpy)

print(<span class="hljs-string">'Check down'</span>)
check_signal(down_naive, down_numpy)
</code></pre><p>Test results are as follows:</p><pre data-type="codeBlock" text="CPU times: user 369 ms, sys: 1.77 ms, total: 370 ms
Wall time: 370 ms
Check up
Num of nan: 199
Num of equal: 49801, Num of not equal: 0, Ratio good: 100.0%
Check down
Num of nan: 199
Num of equal: 49801, Num of not equal: 0, Ratio good: 100.0%
"><code><span class="hljs-attr">CPU times:</span> <span class="hljs-string">user</span> <span class="hljs-number">369</span> <span class="hljs-string">ms,</span> <span class="hljs-attr">sys:</span> <span class="hljs-number">1.77</span> <span class="hljs-string">ms,</span> <span class="hljs-attr">total:</span> <span class="hljs-number">370</span> <span class="hljs-string">ms</span>
<span class="hljs-attr">Wall time:</span> <span class="hljs-number">370</span> <span class="hljs-string">ms</span>
<span class="hljs-string">Check</span> <span class="hljs-string">up</span>
<span class="hljs-attr">Num of nan:</span> <span class="hljs-number">199</span>
<span class="hljs-attr">Num of equal:</span> <span class="hljs-number">49801</span><span class="hljs-string">,</span> <span class="hljs-attr">Num of not equal:</span> <span class="hljs-number">0</span><span class="hljs-string">,</span> <span class="hljs-attr">Ratio good:</span> <span class="hljs-number">100.0</span><span class="hljs-string">%</span>
<span class="hljs-string">Check</span> <span class="hljs-string">down</span>
<span class="hljs-attr">Num of nan:</span> <span class="hljs-number">199</span>
<span class="hljs-attr">Num of equal:</span> <span class="hljs-number">49801</span><span class="hljs-string">,</span> <span class="hljs-attr">Num of not equal:</span> <span class="hljs-number">0</span><span class="hljs-string">,</span> <span class="hljs-attr">Ratio good:</span> <span class="hljs-number">100.0</span><span class="hljs-string">%</span>
</code></pre><h2 id="h-naive-numba-implementation" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Naive Numba Implementation</h2><p>To avoid using python functions during the rolling process, we consider abandoning pandas and using numba njit + numpy .argmax to implement the rolling argmax.</p><pre data-type="codeBlock" text="@nb.njit(nb.int32:, cache=True)
def rolling_argmin(arr, n):
    idx_min = 0
    results = np.empty(len(arr), dtype=np.int32)
    for i, x in enumerate(arr):
        if i &lt; n:
            results[i] = np.argmin(arr[: i + 1])
        else:
            results[i] = np.argmin(arr[i - n + 1: i + 1]) + i - n + 1
    return results

def aroon_numba(df, n):
    low_len = pd.Series(rolling_argmin(df[&apos;low&apos;].values, n))

    high_len = pd.Series(rolling_argmin(-df[&apos;high&apos;].values, n))

    high_len = df.index - high_len
    low_len = df.index - low_len

    aroon_up = 100 * (n - high_len) / n
    aroon_down = 100 * (n - low_len) / n
    return pd.Series(aroon_up), pd.Series(aroon_down)

%time up_numba, down_numba = aroon_numba(df, n)
print(&apos;Check up&apos;)
check_signal(up_naive, up_numba)

print(&apos;Check down&apos;)
check_signal(down_naive, down_numba)
"><code>@nb.njit(nb.int32:, cache<span class="hljs-operator">=</span>True)
def rolling_argmin(arr, n):
    idx_min <span class="hljs-operator">=</span> <span class="hljs-number">0</span>
    results <span class="hljs-operator">=</span> np.empty(len(arr), dtype<span class="hljs-operator">=</span>np.int32)
    <span class="hljs-keyword">for</span> i, x in enumerate(arr):
        <span class="hljs-keyword">if</span> i <span class="hljs-operator">&#x3C;</span> n:
            results[i] <span class="hljs-operator">=</span> np.argmin(arr[: i <span class="hljs-operator">+</span> <span class="hljs-number">1</span>])
        <span class="hljs-keyword">else</span>:
            results[i] <span class="hljs-operator">=</span> np.argmin(arr[i <span class="hljs-operator">-</span> n <span class="hljs-operator">+</span> <span class="hljs-number">1</span>: i <span class="hljs-operator">+</span> <span class="hljs-number">1</span>]) <span class="hljs-operator">+</span> i <span class="hljs-operator">-</span> n <span class="hljs-operator">+</span> <span class="hljs-number">1</span>
    <span class="hljs-keyword">return</span> results

def aroon_numba(df, n):
    low_len <span class="hljs-operator">=</span> pd.Series(rolling_argmin(df[<span class="hljs-string">'low'</span>].values, n))

    high_len <span class="hljs-operator">=</span> pd.Series(rolling_argmin(<span class="hljs-operator">-</span>df[<span class="hljs-string">'high'</span>].values, n))

    high_len <span class="hljs-operator">=</span> df.index <span class="hljs-operator">-</span> high_len
    low_len <span class="hljs-operator">=</span> df.index <span class="hljs-operator">-</span> low_len

    aroon_up <span class="hljs-operator">=</span> <span class="hljs-number">100</span> <span class="hljs-operator">*</span> (n <span class="hljs-operator">-</span> high_len) <span class="hljs-operator">/</span> n
    aroon_down <span class="hljs-operator">=</span> <span class="hljs-number">100</span> <span class="hljs-operator">*</span> (n <span class="hljs-operator">-</span> low_len) <span class="hljs-operator">/</span> n
    <span class="hljs-keyword">return</span> pd.Series(aroon_up), pd.Series(aroon_down)

<span class="hljs-operator">%</span>time up_numba, down_numba <span class="hljs-operator">=</span> aroon_numba(df, n)
print(<span class="hljs-string">'Check up'</span>)
check_signal(up_naive, up_numba)

print(<span class="hljs-string">'Check down'</span>)
check_signal(down_naive, down_numba)
</code></pre><p>Test results are as follows, with no <code>nan</code> values in the signal and all results being correct.</p><pre data-type="codeBlock" text="CPU times: user 27.8 ms, sys: 189 µs, total: 28 ms
Wall time: 28 ms
Check up
Num of nan: 0
Num of equal: 50000, Num of not equal: 0, Ratio good: 100.0%
Check down
Num of nan: 0
Num of equal: 50000, Num of not equal: 0, Ratio good: 100.0%
"><code><span class="hljs-attr">CPU times:</span> <span class="hljs-string">user</span> <span class="hljs-number">27.8</span> <span class="hljs-string">ms,</span> <span class="hljs-attr">sys:</span> <span class="hljs-number">189</span> <span class="hljs-string">µs,</span> <span class="hljs-attr">total:</span> <span class="hljs-number">28</span> <span class="hljs-string">ms</span>
<span class="hljs-attr">Wall time:</span> <span class="hljs-number">28</span> <span class="hljs-string">ms</span>
<span class="hljs-string">Check</span> <span class="hljs-string">up</span>
<span class="hljs-attr">Num of nan:</span> <span class="hljs-number">0</span>
<span class="hljs-attr">Num of equal:</span> <span class="hljs-number">50000</span><span class="hljs-string">,</span> <span class="hljs-attr">Num of not equal:</span> <span class="hljs-number">0</span><span class="hljs-string">,</span> <span class="hljs-attr">Ratio good:</span> <span class="hljs-number">100.0</span><span class="hljs-string">%</span>
<span class="hljs-string">Check</span> <span class="hljs-string">down</span>
<span class="hljs-attr">Num of nan:</span> <span class="hljs-number">0</span>
<span class="hljs-attr">Num of equal:</span> <span class="hljs-number">50000</span><span class="hljs-string">,</span> <span class="hljs-attr">Num of not equal:</span> <span class="hljs-number">0</span><span class="hljs-string">,</span> <span class="hljs-attr">Ratio good:</span> <span class="hljs-number">100.0</span><span class="hljs-string">%</span>
</code></pre><h2 id="h-numba-monotonic-queue-implementation" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Numba + Monotonic Queue Implementation</h2><p>In the naive numba implementation, although time efficiency was improved by avoiding frequent python function calls, its complexity still remains at <code>O(N_candles * N_window)</code>, which is not ideal.</p><p>In the field of algorithms, there is a standard optimal solution for rolling argmax. It utilizes the monotonic queue, and optimizes the complexity down to <code>O(N_candles + N_window)</code>.</p><p>The specific principle of this algorithm is complex and can be referred to in the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://leetcode.cn/problems/sliding-window-maximum/solution/hua-dong-chuang-kou-zui-da-zhi-by-leetco-ki6m/">LeetCode 239 solution</a>, which is considered hard difficulty on LeetCode.</p><pre data-type="codeBlock" text="@nb.njit(nb.int32:, cache=True)
def rolling_argmin_queue(arr, n):
    results = np.empty(len(arr), dtype=np.int32)
    
    head = 0
    tail = 0
    que_idx = np.empty(len(arr), dtype=np.int32)
    for i, x in enumerate(arr[:n]):
        while tail &gt; 0 and arr[que_idx[tail - 1]] &gt; x:
            tail -= 1
        que_idx[tail] = i
        tail += 1
        results[i] = que_idx[0]
    
    for i, x in enumerate(arr[n:], n):
        if que_idx[head] &lt;= i - n:
            head += 1
        while tail &gt; head and arr[que_idx[tail - 1]] &gt; x:
            tail -= 1
        que_idx[tail] = i
        tail += 1
        results[i] = que_idx[head]
    return results
            
def aroon_numba_queue(df, n):
    low_len = pd.Series(rolling_argmin_queue(df[&apos;low&apos;].values, n))
    high_len = pd.Series(rolling_argmin_queue(-df[&apos;high&apos;].values, n))
    
    high_len = df.index - high_len
    low_len = df.index - low_len

    aroon_up = 100 * (n - high_len) / n
    aroon_down = 100 * (n - low_len) / n
    return pd.Series(aroon_up), pd.Series(aroon_down)

%time up_nbque, down_nbque = aroon_numba_queue(df, n)
print(&apos;Check up&apos;)
check_signal(up_naive, up_nbque)

print(&apos;Check down&apos;)
check_signal(down_naive, down_nbque)
"><code>@nb.njit(nb.int32:, cache<span class="hljs-operator">=</span>True)
def rolling_argmin_queue(arr, n):
    results <span class="hljs-operator">=</span> np.empty(len(arr), dtype<span class="hljs-operator">=</span>np.int32)
    
    head <span class="hljs-operator">=</span> <span class="hljs-number">0</span>
    tail <span class="hljs-operator">=</span> <span class="hljs-number">0</span>
    que_idx <span class="hljs-operator">=</span> np.empty(len(arr), dtype<span class="hljs-operator">=</span>np.int32)
    <span class="hljs-keyword">for</span> i, x in enumerate(arr[:n]):
        <span class="hljs-keyword">while</span> tail <span class="hljs-operator">></span> <span class="hljs-number">0</span> and arr[que_idx[tail <span class="hljs-operator">-</span> <span class="hljs-number">1</span>]] <span class="hljs-operator">></span> x:
            tail <span class="hljs-operator">-</span><span class="hljs-operator">=</span> <span class="hljs-number">1</span>
        que_idx[tail] <span class="hljs-operator">=</span> i
        tail <span class="hljs-operator">+</span><span class="hljs-operator">=</span> <span class="hljs-number">1</span>
        results[i] <span class="hljs-operator">=</span> que_idx[<span class="hljs-number">0</span>]
    
    <span class="hljs-keyword">for</span> i, x in enumerate(arr[n:], n):
        <span class="hljs-keyword">if</span> que_idx[head] <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">=</span> i <span class="hljs-operator">-</span> n:
            head <span class="hljs-operator">+</span><span class="hljs-operator">=</span> <span class="hljs-number">1</span>
        <span class="hljs-keyword">while</span> tail <span class="hljs-operator">></span> head and arr[que_idx[tail <span class="hljs-operator">-</span> <span class="hljs-number">1</span>]] <span class="hljs-operator">></span> x:
            tail <span class="hljs-operator">-</span><span class="hljs-operator">=</span> <span class="hljs-number">1</span>
        que_idx[tail] <span class="hljs-operator">=</span> i
        tail <span class="hljs-operator">+</span><span class="hljs-operator">=</span> <span class="hljs-number">1</span>
        results[i] <span class="hljs-operator">=</span> que_idx[head]
    <span class="hljs-keyword">return</span> results
            
def aroon_numba_queue(df, n):
    low_len <span class="hljs-operator">=</span> pd.Series(rolling_argmin_queue(df[<span class="hljs-string">'low'</span>].values, n))
    high_len <span class="hljs-operator">=</span> pd.Series(rolling_argmin_queue(<span class="hljs-operator">-</span>df[<span class="hljs-string">'high'</span>].values, n))
    
    high_len <span class="hljs-operator">=</span> df.index <span class="hljs-operator">-</span> high_len
    low_len <span class="hljs-operator">=</span> df.index <span class="hljs-operator">-</span> low_len

    aroon_up <span class="hljs-operator">=</span> <span class="hljs-number">100</span> <span class="hljs-operator">*</span> (n <span class="hljs-operator">-</span> high_len) <span class="hljs-operator">/</span> n
    aroon_down <span class="hljs-operator">=</span> <span class="hljs-number">100</span> <span class="hljs-operator">*</span> (n <span class="hljs-operator">-</span> low_len) <span class="hljs-operator">/</span> n
    <span class="hljs-keyword">return</span> pd.Series(aroon_up), pd.Series(aroon_down)

<span class="hljs-operator">%</span>time up_nbque, down_nbque <span class="hljs-operator">=</span> aroon_numba_queue(df, n)
print(<span class="hljs-string">'Check up'</span>)
check_signal(up_naive, up_nbque)

print(<span class="hljs-string">'Check down'</span>)
check_signal(down_naive, down_nbque)
</code></pre><p>Test results are as follows, with no <code>nan</code> values in the signal and all results being correct.</p><pre data-type="codeBlock" text="CPU times: user 1.79 ms, sys: 67 µs, total: 1.86 ms
Wall time: 1.87 ms
Check up
Num of nan: 0
Num of equal: 50000, Num of not equal: 0, Ratio good: 100.0%
Check down
Num of nan: 0
Num of equal: 50000, Num of not equal: 0, Ratio good: 100.0%
"><code><span class="hljs-attr">CPU times:</span> <span class="hljs-string">user</span> <span class="hljs-number">1.79</span> <span class="hljs-string">ms,</span> <span class="hljs-attr">sys:</span> <span class="hljs-number">67</span> <span class="hljs-string">µs,</span> <span class="hljs-attr">total:</span> <span class="hljs-number">1.86</span> <span class="hljs-string">ms</span>
<span class="hljs-attr">Wall time:</span> <span class="hljs-number">1.87</span> <span class="hljs-string">ms</span>
<span class="hljs-string">Check</span> <span class="hljs-string">up</span>
<span class="hljs-attr">Num of nan:</span> <span class="hljs-number">0</span>
<span class="hljs-attr">Num of equal:</span> <span class="hljs-number">50000</span><span class="hljs-string">,</span> <span class="hljs-attr">Num of not equal:</span> <span class="hljs-number">0</span><span class="hljs-string">,</span> <span class="hljs-attr">Ratio good:</span> <span class="hljs-number">100.0</span><span class="hljs-string">%</span>
<span class="hljs-string">Check</span> <span class="hljs-string">down</span>
<span class="hljs-attr">Num of nan:</span> <span class="hljs-number">0</span>
<span class="hljs-attr">Num of equal:</span> <span class="hljs-number">50000</span><span class="hljs-string">,</span> <span class="hljs-attr">Num of not equal:</span> <span class="hljs-number">0</span><span class="hljs-string">,</span> <span class="hljs-attr">Ratio good:</span> <span class="hljs-number">100.0</span><span class="hljs-string">%</span>
</code></pre><h1 id="h-conclusion" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Conclusion</h1><p>Comparing the time consumption of various methods, the summary is as follows:</p><pre data-type="codeBlock" text="|               | Time(ms) | Speedup |
| ------------- | -------- | ------- |
| Naive         | 2830     | 1       |
| Numpy         | 370      | 7.65    |
| Naive Numba   | 28       | 101     |
| Numba + Queue | 1.87     | 1513    |
"><code><span class="hljs-operator">|</span>               <span class="hljs-operator">|</span> Time(ms) <span class="hljs-operator">|</span> Speedup <span class="hljs-operator">|</span>
<span class="hljs-operator">|</span> <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span> <span class="hljs-operator">|</span> <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span> <span class="hljs-operator">|</span> <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span> <span class="hljs-operator">|</span>
<span class="hljs-operator">|</span> Naive         <span class="hljs-operator">|</span> <span class="hljs-number">2830</span>     <span class="hljs-operator">|</span> <span class="hljs-number">1</span>       <span class="hljs-operator">|</span>
<span class="hljs-operator">|</span> Numpy         <span class="hljs-operator">|</span> <span class="hljs-number">370</span>      <span class="hljs-operator">|</span> <span class="hljs-number">7.65</span>    <span class="hljs-operator">|</span>
<span class="hljs-operator">|</span> Naive Numba   <span class="hljs-operator">|</span> <span class="hljs-number">28</span>       <span class="hljs-operator">|</span> <span class="hljs-number">101</span>     <span class="hljs-operator">|</span>
<span class="hljs-operator">|</span> Numba <span class="hljs-operator">+</span> Queue <span class="hljs-operator">|</span> <span class="hljs-number">1.87</span>     <span class="hljs-operator">|</span> <span class="hljs-number">1513</span>    <span class="hljs-operator">|</span>
</code></pre>]]></content:encoded>
            <author>lostleaf@newsletter.paragraph.com (lostleaf.eth)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/327d545c252954ab077e0153317b605db7f6e71b7e4b8731ca73e7dd1c95e2f0.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[How to install Ta-lib in Python Anaconda environment]]></title>
            <link>https://paragraph.com/@lostleaf/how-to-install-ta-lib-in-python-anaconda-environment</link>
            <guid>Agw0kmqbRqOGOD4BkIi1</guid>
            <pubDate>Mon, 08 Apr 2024 07:10:49 GMT</pubDate>
            <description><![CDATA[TA-Lib is a widely utilized library for technical analysis in quantitative trend-following strategies. The installation process for TA-Lib can be quite challenging, leading users to spend a significant amount of time on setup. In this article, we will discuss a straightforward method for installing TA-Lib within the Python Anaconda environment as of today (2023–06–23). For all 64-bit operating systems, including Windows, Linux, and macOS (Arm64 M1-M3 CPUs are supported), installation can be c...]]></description>
            <content:encoded><![CDATA[<p>TA-Lib is a widely utilized library for technical analysis in quantitative trend-following strategies. The installation process for TA-Lib can be quite challenging, leading users to spend a significant amount of time on setup.</p><p>In this article, we will discuss a straightforward method for installing TA-Lib within the Python Anaconda environment as of today (2023–06–23).</p><p>For all 64-bit operating systems, including Windows, Linux, and macOS (Arm64 M1-M3 CPUs are supported), installation can be completed using a single command:</p><pre data-type="codeBlock" text="&gt; conda install -c conda-forge ta-lib
"><code><span class="hljs-operator">></span> conda install <span class="hljs-operator">-</span>c conda<span class="hljs-operator">-</span>forge ta<span class="hljs-operator">-</span>lib
</code></pre><p>After installation, we can verify with the following:</p><pre data-type="codeBlock" text="&gt; python -c &quot;import talib; print(talib.__version__)&quot;
0.4.19
"><code><span class="hljs-operator">></span> python <span class="hljs-operator">-</span>c <span class="hljs-string">"import talib; print(talib.__version__)"</span>
<span class="hljs-number">0</span><span class="hljs-number">.4</span><span class="hljs-number">.19</span>
</code></pre><p>Support for the arm64 version of macOS has been recently added. With the latest Anaconda release, <em>Anaconda3–2023.03–1-MacOSX-arm64</em>, you can verify the installation using:</p><pre data-type="codeBlock" text="&gt; python -c &quot;import talib, platform; print(platform.processor(), talib.__version__)&quot;
arm 0.4.19
"><code><span class="hljs-operator">></span> python <span class="hljs-operator">-</span>c <span class="hljs-string">"import talib, platform; print(platform.processor(), talib.__version__)"</span>
arm <span class="hljs-number">0</span><span class="hljs-number">.4</span><span class="hljs-number">.19</span>
</code></pre><p>This confirms that the library has been installed correctly on the Arm64 version of macOS.</p>]]></content:encoded>
            <author>lostleaf@newsletter.paragraph.com (lostleaf.eth)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/4fc6cbdf55494c9b82b6717a70a860af3a5c7ec028d5775dfdf6ccc124189183.png" length="0" type="image/png"/>
        </item>
    </channel>
</rss>