<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Oliver Zehentleitner</title>
    <description>The latest articles on DEV Community by Oliver Zehentleitner (@oliverzehentleitner).</description>
    <link>https://dev.to/oliverzehentleitner</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2124155%2F0d79bc59-db7b-4879-bd1a-36dc2f79d1c2.png</url>
      <title>DEV Community: Oliver Zehentleitner</title>
      <link>https://dev.to/oliverzehentleitner</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/oliverzehentleitner"/>
    <language>en</language>
    <item>
      <title>Session memory is not project memory. It fixes the same complaint.</title>
      <dc:creator>Oliver Zehentleitner</dc:creator>
      <pubDate>Thu, 24 Sep 2026 10:55:58 +0000</pubDate>
      <link>https://dev.to/oliverzehentleitner/session-memory-is-not-project-memory-it-fixes-the-same-complaint-198i</link>
      <guid>https://dev.to/oliverzehentleitner/session-memory-is-not-project-memory-it-fixes-the-same-complaint-198i</guid>
      <description>&lt;p&gt;&lt;strong&gt;"My coding agent forgets everything between sessions." Two different things answer that, and they are not competing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The most common complaint about coding agents is the same on every forum: a new session starts from nothing. The explanation you gave yesterday is gone, the decision you made together is gone, the wrong turn the agent took and you corrected is gone, and tomorrow it proposes it again. Everyone who has worked with an agent for more than a week knows the feeling.&lt;/p&gt;

&lt;p&gt;Two different things answer that complaint. They get compared as if they were rivals, and they are layers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Session memory: what the agent keeps for itself
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://code.claude.com/docs/en/memory" rel="noopener noreferrer"&gt;Claude Code's auto memory&lt;/a&gt; is the clearest example, because it is built in and on by default. As it works, Claude writes notes for itself: your role and preferences, corrections you gave, project context it can't derive from the code, ongoing work, decisions, and references to things outside the repository. The notes live in a directory on your machine, one per repository, and the first part of the file is loaded into every session. No setup, nothing to decide.&lt;/p&gt;

&lt;p&gt;Three properties define it, and none of them is a flaw:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;It belongs to one person on one machine.&lt;/strong&gt; The directory is under your home, not in the repository. A teammate's clone does not have it. Your second laptop does not have it. That is by design: a memory that holds &lt;em&gt;your&lt;/em&gt; preferences and &lt;em&gt;your&lt;/em&gt; corrections should not automatically travel with the code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;It belongs to one tool.&lt;/strong&gt; The notes are Claude Code's; Codex, Cursor or Copilot on the same repository do not inherit them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;It is not reviewed with the code.&lt;/strong&gt; You can inspect and edit the memory, but it is not part of the repository's normal Git and pull-request review flow.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the right shape for what it holds. Your preferences are yours. A note like "keep explanations short unless I ask for detail" is between you and your agent. Nobody needs that in a shared project file.&lt;/p&gt;

&lt;p&gt;Claude Code also has repository-level &lt;a href="https://code.claude.com/docs/en/memory" rel="noopener noreferrer"&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt; and &lt;code&gt;AGENTS.md&lt;/code&gt;&lt;/a&gt; files. Those are shared project instructions rather than auto memory: rules, workflows, architecture and things Claude should do. They can contain rationale too, but rationale is not their dedicated job. The distinction here is specifically between Claude's local auto memory and a repository-native rationale layer.&lt;/p&gt;

&lt;p&gt;The complaint session memory answers is: &lt;em&gt;the agent forgot what I told it.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Project memory: what the repository keeps for everyone
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://oliver-zehentleitner.github.io/repo-native-project-memory/" rel="noopener noreferrer"&gt;A repository already is a project's memory&lt;/a&gt;, and has been for decades. The README says what it is, the docs how to use it, the tests what it must do, the changelog what changed, the history who changed it and when. Whoever has the clone has all of it, in every tool, on every machine, reviewed in every pull request.&lt;/p&gt;

&lt;p&gt;What the classic layout never had a clear place for is the &lt;em&gt;why&lt;/em&gt;: the decision, the alternative that was rejected and the reason it lost, the constraint the code doesn't show, the workaround that must not be cleaned up. That reasoning used to live in people's heads, and left with them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://keepthewhy.com/" rel="noopener noreferrer"&gt;Keep the Why&lt;/a&gt; is the convention and the agent skill for that layer: the agent writes the reasoning into &lt;a href="https://keepthewhy.com/specification/" rel="noopener noreferrer"&gt;&lt;code&gt;context/&lt;/code&gt;&lt;/a&gt; as it comes up in the session, as Markdown, committed with the code.&lt;/p&gt;

&lt;p&gt;Three properties again, and they are the mirror image:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;It belongs to the project.&lt;/strong&gt; It is in the repository, so the clone carries it. Your teammate's agent can read the same file yours wrote.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;It belongs to no tool.&lt;/strong&gt; Any agent that can read the repository can read it, and switching tools does not remove the information.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;It is reviewed.&lt;/strong&gt; The why arrives in the same pull request as the code, and a wrong entry is a diff somebody can see.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The complaint project memory answers is: &lt;em&gt;the agent forgot why the code is the way it is.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The same complaint, two answers
&lt;/h2&gt;

&lt;p&gt;"The agent forgets" covers several different losses. Sort them and the answer sorts itself:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What the agent forgot&lt;/th&gt;
&lt;th&gt;Which memory answers it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;that you prefer short commit messages&lt;/td&gt;
&lt;td&gt;session memory: personal, one tool, one machine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;what you two did yesterday afternoon&lt;/td&gt;
&lt;td&gt;session memory, or &lt;code&gt;git log&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;where the staging dashboard is&lt;/td&gt;
&lt;td&gt;session memory as a personal reference, or the README if the team needs it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;that the plain retry loop was rejected, and why&lt;/td&gt;
&lt;td&gt;project memory, the why layer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;that the ugly workaround exists because of a rate limiter&lt;/td&gt;
&lt;td&gt;project memory, the why layer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;that a teammate's agent already found the migration trap&lt;/td&gt;
&lt;td&gt;project memory: it has to reach the other machine&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last three are the ones that come back as production incidents when they are lost.&lt;/p&gt;

&lt;p&gt;A session memory may remember those facts locally. What it cannot do by itself is make them shared project knowledge across people, machines and tools.&lt;/p&gt;

&lt;p&gt;That is the important distinction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why you keep both
&lt;/h2&gt;

&lt;p&gt;The temptation is to pick one. Both directions are wrong.&lt;/p&gt;

&lt;p&gt;Putting the why only into a personal memory is where many people start, because it is zero setup. It works until the second person, the second machine or the second tool. At that point the reasoning may still exist, but it is trapped in a memory the rest of the project does not carry.&lt;/p&gt;

&lt;p&gt;Putting personal preferences into the repository is the opposite mistake. A shared &lt;code&gt;context/&lt;/code&gt; that holds one developer's habits is noise for everyone else, and a privacy problem on top.&lt;/p&gt;

&lt;p&gt;Keep the Why refuses that on purpose: personal settings live outside the project, in the developer's own home directory, and the skill does not record who said what.&lt;/p&gt;

&lt;p&gt;So: session memory for what is yours, project memory for what belongs to the project.&lt;/p&gt;

&lt;p&gt;Leave auto memory on. Give the repository a why layer.&lt;/p&gt;

&lt;p&gt;They may remember the same fact. Only one of them makes it part of the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  The test
&lt;/h2&gt;

&lt;p&gt;Clone the repository on a machine that has never seen it. Open it with a tool you have not used on it before. Hand it to a colleague.&lt;/p&gt;

&lt;p&gt;What is still available to the agent is project memory. What has disappeared was session memory, however good that memory was on your laptop.&lt;/p&gt;

&lt;p&gt;The complaint "my agent forgets everything between sessions" is really two complaints, and only one of them was ever about the session.&lt;/p&gt;




&lt;p&gt;I hope you found this informative and useful.&lt;/p&gt;

&lt;p&gt;Follow me on &lt;a href="https://github.com/oliver-zehentleitner" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, &lt;a href="https://bsky.app/profile/o-zehentleitner.bsky.social" rel="noopener noreferrer"&gt;Bluesky&lt;/a&gt;, &lt;a href="https://burningboard.net/@oliverzehentleitner" rel="noopener noreferrer"&gt;Mastodon&lt;/a&gt;, &lt;a href="https://x.com/unicorn_oz" rel="noopener noreferrer"&gt;X&lt;/a&gt;, and &lt;a href="https://www.linkedin.com/in/oliver-zehentleitner/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, or join &lt;a href="https://t.me/unicorndevs" rel="noopener noreferrer"&gt;Telegram&lt;/a&gt; for updates on my latest publications. Constructive feedback is always appreciated.&lt;/p&gt;

&lt;p&gt;Thank you for reading, and happy coding! ¯\_(ツ)_/¯&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agentskills</category>
      <category>programming</category>
      <category>documentation</category>
    </item>
    <item>
      <title>Keep the Why vs. Claude Code Auto Memory vs. MemoryCustodian vs. AgentsRoom</title>
      <dc:creator>Oliver Zehentleitner</dc:creator>
      <pubDate>Wed, 23 Sep 2026 07:15:56 +0000</pubDate>
      <link>https://dev.to/oliverzehentleitner/keep-the-why-vs-claude-code-auto-memory-vs-memorycustodian-vs-agentsroom-436d</link>
      <guid>https://dev.to/oliverzehentleitner/keep-the-why-vs-claude-code-auto-memory-vs-memorycustodian-vs-agentsroom-436d</guid>
      <description>&lt;p&gt;&lt;strong&gt;Four tools that get called project memory. Three questions sort them. One table, as of September 2026.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;"Project memory for coding agents" now means at least four different things, and most comparisons put them in one list as if they competed. They mostly don't. Before comparing tools, ask three questions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where does it live?&lt;/strong&gt; In the repository, in a directory on one machine, or on a server. This decides who gets it with a clone and whether a pull request can review it. It also decides a lot about whether the memory can be audited later: who changed it, when, and alongside which code change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who can read it?&lt;/strong&gt; One person, everyone who opens the project in one particular app, or anyone who can open the repository.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What does it remember?&lt;/strong&gt; This is the question that gets skipped. A memory can hold three different things, and they are layers, not rivals: what &lt;em&gt;happened&lt;/em&gt; (a session, a transcript, a fix that worked), where the project &lt;em&gt;stands&lt;/em&gt; (status, next action, what shipped), and &lt;em&gt;why&lt;/em&gt; the project became what it is (the decision, the rejected alternative, the constraint the code doesn't show).&lt;/p&gt;

&lt;p&gt;Run the names that usually appear in these lists through those questions and most of them leave the room. mem0, Letta, Zep and MemPalace are external long-term memory systems centered on what an agent or user should remember across sessions, rather than rationale stored with the repository. Aider, Mastra, Dify and Langflow are a coding agent and three frameworks with memory as a feature. AgentMemory primarily captures and retrieves session observations in an external local store. Beads, projectmemory.app and Storyflow are closer to &lt;em&gt;where the project stands&lt;/em&gt;: Beads through a persistent dependency-aware task graph, the latter two through project state living primarily in their own workspace rather than in repository-native rationale files. projectmem keeps a gitignored event log and commits a digest of it, a session layer with a repo-native summary. What is left is two repo-native tools that hold rationale, Claude Code's built-in memory, and a server-backed project-memory implementation. Those four are the table.&lt;/p&gt;

&lt;h2&gt;
  
  
  The table
&lt;/h2&gt;

&lt;p&gt;Checked on 2026-09-22 against &lt;a href="https://code.claude.com/docs/en/memory" rel="noopener noreferrer"&gt;Claude Code's memory page&lt;/a&gt;, &lt;a href="https://github.com/waittim/MemoryCustodian" rel="noopener noreferrer"&gt;MemoryCustodian's README&lt;/a&gt;, &lt;a href="https://agentsroom.dev/features/project-memory" rel="noopener noreferrer"&gt;AgentsRoom's Project Memory page&lt;/a&gt; and &lt;a href="https://keepthewhy.com/specification/" rel="noopener noreferrer"&gt;the Keep the Why specification&lt;/a&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Keep the Why&lt;/th&gt;
&lt;th&gt;Claude Code Auto Memory&lt;/th&gt;
&lt;th&gt;MemoryCustodian&lt;/th&gt;
&lt;th&gt;AgentsRoom Project Memory&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Where it lives&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;context/&lt;/code&gt; in the repository (or an existing decisions folder)&lt;/td&gt;
&lt;td&gt;by default &lt;code&gt;~/.claude/projects/&amp;lt;project&amp;gt;/memory/&lt;/code&gt; on the user's machine; the auto-memory directory can be configured&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;docs/memory/&lt;/code&gt; in the repository&lt;/td&gt;
&lt;td&gt;AgentsRoom's server, keyed by a project id committed in the repo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Who reads it&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;anyone with the clone, the reviewer in the pull request, any agent&lt;/td&gt;
&lt;td&gt;one user in Claude Code wherever that memory directory is available&lt;/td&gt;
&lt;td&gt;anyone with the clone&lt;/td&gt;
&lt;td&gt;anyone logged in to AgentsRoom who has the project id&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Versioned / auditable&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Git, with the code&lt;/td&gt;
&lt;td&gt;no, unless the configured directory is versioned separately&lt;/td&gt;
&lt;td&gt;Git, with the code&lt;/td&gt;
&lt;td&gt;a note history on the server; not Git&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Vendor-independent&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Agent Skills format, any supporting agent; the content is Markdown&lt;/td&gt;
&lt;td&gt;Claude Code only&lt;/td&gt;
&lt;td&gt;Python CLI plus skills for Codex, Claude Code and Gemini; the content is Markdown&lt;/td&gt;
&lt;td&gt;any CLI, but only inside the AgentsRoom app, logged in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;What has to run&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;nothing; a linter and a read-only dashboard are optional&lt;/td&gt;
&lt;td&gt;nothing, built in&lt;/td&gt;
&lt;td&gt;a Python CLI: &lt;code&gt;init&lt;/code&gt;, &lt;code&gt;add&lt;/code&gt;, &lt;code&gt;check&lt;/code&gt;, &lt;code&gt;migrate&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;a desktop app, an account, an MCP server; agents need a connection for every read and write&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Selective loading&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;context/index.md&lt;/code&gt;, one line per topic under a fixed 36-heading skeleton; the agent opens the topic the task touches&lt;/td&gt;
&lt;td&gt;the first 200 lines or 25 KB of &lt;code&gt;MEMORY.md&lt;/code&gt;, every session&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;manifest.md&lt;/code&gt; routing rules, then &lt;code&gt;brief.md&lt;/code&gt;, then the task's files&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;memory_list&lt;/code&gt; at the start of a task, &lt;code&gt;memory_get&lt;/code&gt; per note&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;What gets written, by whom&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;rationale synthesized by the agent from the conversation, with Type, Status and Evidence fields; superseded entries kept; confirmation is a project setting&lt;/td&gt;
&lt;td&gt;notes Claude writes for itself, four kinds: user, feedback, project, reference; no confirmation&lt;/td&gt;
&lt;td&gt;typed files for decisions, constraints and rejected paths, with stable IDs and evidence attribution; the agent writes through the CLI&lt;/td&gt;
&lt;td&gt;free-form notes in five folders, wiki-linked, written by the agent through &lt;code&gt;memory_save&lt;/code&gt;, no confirmation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Where the others are better
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Claude Code Auto Memory&lt;/strong&gt; has zero setup, is on by default, and holds the layer Keep the Why deliberately leaves out: who you are, how you like to work, the corrections you gave last Tuesday. Keep the Why keeps that out of the repository on purpose, nobody's preferences belong in a shared &lt;code&gt;context/&lt;/code&gt;, so the two are complementary. By default, auto memory does not follow a change of tool, a teammate's clone or a second machine. That is not a bug. It was never meant to be repository-native project memory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MemoryCustodian&lt;/strong&gt; is the closest neighbour: repo-native, plain Markdown, manifest routing, evidence per entry, a &lt;code&gt;do-not-use.md&lt;/code&gt; for rejected paths. The differences are design choices. A protocol-centered set of core files plus routed area files, or topic files the project names itself. CLI-enforced evidence and identity checks, or a project-level confirmation setting. If you want the memory to be a program with commands, MemoryCustodian is the honest pick. If you want it to be text the agent maintains and nothing else, that is Keep the Why.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AgentsRoom Project Memory&lt;/strong&gt; has the best reading experience of the four: a graph with wiki-links as edges, fuzzy search, four scopes, update-in-place with an append mode. All of it runs on AgentsRoom's server. The notes are not in Git, not reviewable in a pull request, not readable outside the app, and the feature page says it plainly: "treat the memory of a public repository as public." It is project memory that lives with the project id, not in the project. For a team that has moved its whole agent workflow into AgentsRoom, a fine trade. For a repository that will outlive the app, the trade the repo-native idea exists to avoid.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Keep the Why doesn't do, on purpose
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No similarity search.&lt;/strong&gt; Retrieval is the index and the topic name; entries are synthesized and read in full, not stored verbatim and ranked. &lt;code&gt;context/&lt;/code&gt; is Markdown, any search tool can index it, none is shipped.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No commit gating.&lt;/strong&gt; No Git hooks; the linter checks the form of an entry, never a decision. Prevention happens in the session, before the change: with the entry in place, 0 of 10 fresh sessions proposed a rejected simplification; without it, 7 of 10 did (&lt;a href="https://blog.technopathy.club/what-happens-when-a-coding-agent-forgets-why-a-change-was-rejected" rel="noopener noreferrer"&gt;the experiment&lt;/a&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No UI that holds anything.&lt;/strong&gt; &lt;code&gt;context/&lt;/code&gt; renders on GitHub for anyone who can open the repository; Git provides the history and review trail, while the &lt;a href="https://keepthewhy.com/dashboard/live/" rel="noopener noreferrer"&gt;read-only dashboard&lt;/a&gt; provides another view of the same repository-held memory without storing a copy.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The reasons for each are in the &lt;a href="https://keepthewhy.com/faq/" rel="noopener noreferrer"&gt;FAQ&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that is true of all four
&lt;/h2&gt;

&lt;p&gt;Every one of these tools lives on the quality of what gets written into it. A store full of transcripts, five folders of notes, a &lt;code&gt;decisions.md&lt;/code&gt; with stable IDs, a &lt;code&gt;context/&lt;/code&gt; with Evidence fields: none is worth anything if the content is thin, and none can fix that afterwards. The differences are in who writes, when, and what shape the writing has to take.&lt;/p&gt;

&lt;p&gt;Keep the Why's answer is less about who writes the memory than what gets written. The agent synthesizes rather than records: a rejected alternative is written as a rejected alternative with the reason it lost, not as a transcript or general note a later reader has to interpret. The linter keeps the form honest. The rest is Markdown in Git, and Git already knows how to version it, distribute it and put it in front of a reviewer.&lt;/p&gt;

&lt;p&gt;Session memory remembers what happened. Project state remembers where the project is. The why layer remembers why it became what it is. Three layers. Pick one tool per layer if you need all three, and don't let a comparison, this one included, tell you they are rivals.&lt;/p&gt;




&lt;p&gt;I hope you found this informative and useful.&lt;/p&gt;

&lt;p&gt;Follow me on &lt;a href="https://github.com/oliver-zehentleitner" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, &lt;a href="https://bsky.app/profile/o-zehentleitner.bsky.social" rel="noopener noreferrer"&gt;Bluesky&lt;/a&gt;, &lt;a href="https://burningboard.net/@oliverzehentleitner" rel="noopener noreferrer"&gt;Mastodon&lt;/a&gt;, &lt;a href="https://x.com/unicorn_oz" rel="noopener noreferrer"&gt;X&lt;/a&gt;, and &lt;a href="https://www.linkedin.com/in/oliver-zehentleitner/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, or join &lt;a href="https://t.me/unicorndevs" rel="noopener noreferrer"&gt;Telegram&lt;/a&gt; for updates on my latest publications. Constructive feedback is always appreciated.&lt;/p&gt;

&lt;p&gt;Thank you for reading, and happy coding! ¯\_(ツ)_/¯&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>agentskills</category>
      <category>documentation</category>
    </item>
    <item>
      <title>Keep the Why is not another workflow</title>
      <dc:creator>Oliver Zehentleitner</dc:creator>
      <pubDate>Tue, 22 Sep 2026 14:55:22 +0000</pubDate>
      <link>https://dev.to/oliverzehentleitner/keep-the-why-is-not-another-workflow-3fd7</link>
      <guid>https://dev.to/oliverzehentleitner/keep-the-why-is-not-another-workflow-3fd7</guid>
      <description>&lt;p&gt;&lt;strong&gt;Install it, initialize the project, keep coding.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There is a misconception about Keep the Why that I keep running into.&lt;/p&gt;

&lt;p&gt;People see a structured &lt;code&gt;context/&lt;/code&gt; directory containing decisions, rejected alternatives, workarounds, constraints and incident learnings and assume there must be discipline behind it.&lt;/p&gt;

&lt;p&gt;Someone has to remember what to record.&lt;/p&gt;

&lt;p&gt;Someone has to tell the agent when something is important.&lt;/p&gt;

&lt;p&gt;Someone has to maintain another documentation system.&lt;/p&gt;

&lt;p&gt;That would defeat the point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep the Why is designed so that after a very small initial setup, you keep working exactly as before.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The agent does the remembering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install the skill
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/oliver-zehentleitner/keep-the-why" rel="noopener noreferrer"&gt;Keep the Why&lt;/a&gt; is an Agent Skill using the open, cross-agent skill format.&lt;/p&gt;

&lt;p&gt;For more than 70 supported agents, the recommended installation is documented on the &lt;a href="https://keepthewhy.com/installation/" rel="noopener noreferrer"&gt;installation page&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add https://github.com/oliver-zehentleitner/keep-the-why/tree/latest/skills/keep-the-why
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are also installation paths for GitHub CLI, Claude Code plugins, GitHub Copilot CLI, Codex plugins, Cursor, asm and manual installation.&lt;/p&gt;

&lt;p&gt;There is no service to deploy. No database. No daemon. No account.&lt;/p&gt;

&lt;p&gt;What you install is essentially the instructions that teach your coding agent how to recognize and preserve the reasoning behind a project.&lt;/p&gt;

&lt;p&gt;If you start a session after installing the skill, you do not need a separate "load Keep the Why" step. Asking the agent to set it up in the project is enough to activate it.&lt;/p&gt;

&lt;p&gt;If the agent session was already running before you installed the skill, tell that session to load Keep the Why once - or point it directly at its &lt;code&gt;SKILL.md&lt;/code&gt;. After project setup, the start path handles future sessions automatically. The supported mechanisms are documented under &lt;a href="https://keepthewhy.com/autostart/" rel="noopener noreferrer"&gt;autostart&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initialize the project
&lt;/h2&gt;

&lt;p&gt;Inside the project directory, follow the short &lt;a href="https://keepthewhy.com/setup/" rel="noopener noreferrer"&gt;project setup&lt;/a&gt; and tell the agent something like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Initialize Keep the Why in this project.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This explicit request is intentional. Keep the Why does not silently turn itself on just because you installed it. A repository has to opt in once.&lt;/p&gt;

&lt;p&gt;The agent then presents the project setup. With the defaults, there is very little to decide.&lt;/p&gt;

&lt;p&gt;Among other things, the setup covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;where the project's why-knowledge should live - &lt;code&gt;context/&lt;/code&gt; by default unless an existing decision location is a better fit&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;how you want to start capturing project knowledge&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;proactive capture during normal work&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;asking before writing only when something is genuinely unclear&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;no constant questions for issue or ticket references&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;structural linting where supported and detected&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;loading Keep the Why automatically in future sessions&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The wizard is presented as a list with defaults already filled in where the setup defines them.&lt;/p&gt;

&lt;p&gt;You can effectively answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;defaults&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There is a second short list for your personal workflow preferences.&lt;/p&gt;

&lt;p&gt;Again:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;defaults&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And you are done.&lt;/p&gt;

&lt;p&gt;This is not a configuration project. It is meant to be something you can set up in roughly the time it takes to explain why you wanted it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then forget about Keep the Why
&lt;/h2&gt;

&lt;p&gt;This is probably the most important part.&lt;/p&gt;

&lt;p&gt;After setup, there is no Keep the Why workflow. You do not periodically stop coding to document decisions, decide whether something belongs in &lt;code&gt;context/&lt;/code&gt;, or tell the agent:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Store this in project memory.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You work with your coding agent normally. You ask it to implement something, debug a production problem, discuss an architectural choice, reject an apparently simpler implementation because of a constraint, or discover why a strange workaround exists.&lt;/p&gt;

&lt;p&gt;That reasoning is already happening inside the conversation. Keep the Why's job is simply to stop throwing it away.&lt;/p&gt;

&lt;p&gt;With the default setup, the agent proactively notices reasoning worth preserving and captures it while the work happens. If the situation is clear, it does not need to interrupt you. If something important is genuinely ambiguous, it asks.&lt;/p&gt;

&lt;p&gt;That is the default.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually gets left behind
&lt;/h2&gt;

&lt;p&gt;The result is deliberately boring: plain Markdown in the project.&lt;/p&gt;

&lt;p&gt;A real example from the Keep the Why documentation, the file context/retries.md, looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Retries&lt;/span&gt;

&lt;span class="gu"&gt;## Why retry_with_jitter isn't a plain retry loop&lt;/span&gt;

&lt;span class="gs"&gt;**Type:**&lt;/span&gt; constraint
&lt;span class="gs"&gt;**Status:**&lt;/span&gt; active
&lt;span class="gs"&gt;**Evidence:**&lt;/span&gt; confirmed
&lt;span class="gs"&gt;**Source:**&lt;/span&gt; discovered while considering simplifying it, 2026-07-22

The payment gateway's rate limiter returns 429 with a per-request
Retry-After header. A fixed-delay retry loop would frequently retry
before the limiter resets, causing repeated 429s under load.

&lt;span class="gs"&gt;**Considered:**&lt;/span&gt; replacing it with a plain retry loop, since the wrapper
looked like unnecessary complexity with nothing documenting why.
Not adopted once the Retry-After behavior surfaced during review.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That entry exists because somebody considered simplifying code that looked unnecessarily complicated.&lt;/p&gt;

&lt;p&gt;Without the reason, a later agent sees complexity and may propose the same simplification again. With the reason in the repository, the next session can understand why the code looks the way it does without anyone having to retell the story.&lt;/p&gt;

&lt;p&gt;That is what "the agent does the remembering" means in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your agent stops being a goldfish
&lt;/h2&gt;

&lt;p&gt;A coding agent usually enters a repository with an interesting asymmetry.&lt;/p&gt;

&lt;p&gt;It has the source code, tests, documentation and Git history, but it does not necessarily have the experience that produced them.&lt;/p&gt;

&lt;p&gt;A previous session may have spent an hour discovering why an obvious implementation does not work. The next session sees the same code and happily proposes the same implementation again.&lt;/p&gt;

&lt;p&gt;That is the goldfish problem.&lt;/p&gt;

&lt;p&gt;Keep the Why puts that missing experience into the repository too.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;project/
├── src/
├── tests/
├── docs/
├── context/
├── README.md
└── .keep-the-why
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now a future session can inherit more than the result.&lt;/p&gt;

&lt;p&gt;It can inherit the reasoning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Git already solves the distribution problem
&lt;/h2&gt;

&lt;p&gt;Keep the Why deliberately does not build another synchronization system.&lt;/p&gt;

&lt;p&gt;The memory is part of the project. &lt;code&gt;context/&lt;/code&gt; is Markdown. &lt;code&gt;.keep-the-why&lt;/code&gt; records that the project has opted in and how it is configured. Both travel through Git like the rest of the repository.&lt;/p&gt;

&lt;p&gt;Clone the repository somewhere else and the project memory comes with it. Switch machine and it comes with it. Open another branch and it comes with it. A different developer gets it. A different agent gets it. A different session gets it.&lt;/p&gt;

&lt;p&gt;The personal workflow preferences of each developer remain personal, but the actual project knowledge is shared.&lt;/p&gt;

&lt;p&gt;There is no memory database belonging to one AI provider and no conversation history that only one person can access.&lt;/p&gt;

&lt;p&gt;The project carries the experience with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The PR contains the why too
&lt;/h2&gt;

&lt;p&gt;Yes, this means a change can include both:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/...
context/...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I increasingly think this is a feature, not a cost.&lt;/p&gt;

&lt;p&gt;A reviewer normally receives the result of the coding process. The code changed. Maybe some tests changed. But much of the reasoning that led there disappeared with the coding session.&lt;/p&gt;

&lt;p&gt;With repo-native project memory, some of that reasoning can arrive in the same pull request.&lt;/p&gt;

&lt;p&gt;The reviewer can see not just:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This implementation changed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;but also:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is why it changed, what alternative was rejected and which constraint mattered.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And the reviewer does not have to be human.&lt;/p&gt;

&lt;p&gt;A coding agent can produce a change and capture the reasoning that surfaced while producing it. A review agent can read the same &lt;code&gt;context/&lt;/code&gt; when reviewing the change.&lt;/p&gt;

&lt;p&gt;In that sense, &lt;code&gt;context/&lt;/code&gt; becomes a small but useful agent-to-agent communication layer. The coder passes experience forward; the reviewer can challenge it, verify it or notice when the code contradicts it.&lt;/p&gt;

&lt;p&gt;And because it is ordinary Markdown in the PR, humans can do exactly the same thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Teams accumulate experience instead of isolated sessions
&lt;/h2&gt;

&lt;p&gt;This becomes more interesting with multiple developers and agents.&lt;/p&gt;

&lt;p&gt;Normally project knowledge spreads through conversations. One developer discovers something, another learns it in Slack, someone explains it during a call, and an agent discovers it independently three months later. Another agent may repeat the rejected approach six months after that.&lt;/p&gt;

&lt;p&gt;Eventually the person who understood the original reason leaves. The code remains. The experience does not.&lt;/p&gt;

&lt;p&gt;Keep the Why changes where that experience accumulates. That idea is the core of &lt;a href="https://oliver-zehentleitner.github.io/repo-native-project-memory/" rel="noopener noreferrer"&gt;repo-native project memory&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Not in one developer's head, one agent's session memory or one vendor's conversation database.&lt;/p&gt;

&lt;p&gt;In the project.&lt;/p&gt;

&lt;p&gt;That means agents and developers working at different times can benefit from each other's discoveries. The knowledge compounds without everyone having to become more disciplined about documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The complete workflow
&lt;/h2&gt;

&lt;p&gt;The initial setup is basically this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Install Keep the Why
        ↓
"Initialize Keep the Why in this project"
        ↓
Accept the defaults
        ↓
Keep coding
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the current agent session was already running before the skill was installed, load it once in that session first. That is the exception, not the normal workflow.&lt;/p&gt;

&lt;p&gt;After setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;normal development
        ↓
useful reasoning appears naturally
        ↓
the agent notices it
        ↓
context/ is updated
        ↓
Git carries it with the project
        ↓
future humans and agents inherit it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the system.&lt;/p&gt;

&lt;p&gt;You should not have to become the librarian for your AI agent.&lt;/p&gt;

&lt;p&gt;You should not have to remember to preserve the reasoning.&lt;/p&gt;

&lt;p&gt;And you should not have to explain the same project history to every fresh session.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install the skill. Initialize the project. Keep coding.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If Keep the Why requires you to constantly think about Keep the Why, it has failed at its job.&lt;/p&gt;




&lt;p&gt;I hope you found this informative and useful.&lt;/p&gt;

&lt;p&gt;Follow me on &lt;a href="https://github.com/oliver-zehentleitner" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, &lt;a href="https://bsky.app/profile/o-zehentleitner.bsky.social" rel="noopener noreferrer"&gt;Bluesky&lt;/a&gt;, &lt;a href="https://burningboard.net/@oliverzehentleitner" rel="noopener noreferrer"&gt;Mastodon&lt;/a&gt;, &lt;a href="https://x.com/unicorn_oz" rel="noopener noreferrer"&gt;X&lt;/a&gt;, and &lt;a href="https://www.linkedin.com/in/oliver-zehentleitner/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, or join &lt;a href="https://t.me/unicorndevs" rel="noopener noreferrer"&gt;Telegram&lt;/a&gt; for updates on my latest publications. Constructive feedback is always appreciated.&lt;/p&gt;

&lt;p&gt;Thank you for reading, and happy coding! ¯\_(ツ)_/¯&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devtools</category>
      <category>opensource</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Your repository already is your project's memory. One layer was missing.</title>
      <dc:creator>Oliver Zehentleitner</dc:creator>
      <pubDate>Mon, 14 Sep 2026 14:37:33 +0000</pubDate>
      <link>https://dev.to/oliverzehentleitner/your-repository-already-is-your-projects-memory-one-layer-was-missing-2ld7</link>
      <guid>https://dev.to/oliverzehentleitner/your-repository-already-is-your-projects-memory-one-layer-was-missing-2ld7</guid>
      <description>&lt;p&gt;Every coding agent starts the day as a goldfish. It can read the code, reason about it, and still have no idea why any of it is the way it is. So it proposes the simplification you rejected in March, for the reason you rejected it in March, and you explain it again.&lt;/p&gt;

&lt;p&gt;A whole product category has grown around that: &lt;a href="https://blog.technopathy.club/keep-the-why-project-memory-for-humans-and-ai-agents" rel="noopener noreferrer"&gt;project memory for agents&lt;/a&gt;. Databases, MCP servers, knowledge graphs, subscriptions. I think much of it starts from the wrong question, because most of the memory already exists.&lt;/p&gt;

&lt;p&gt;That is the idea behind &lt;a href="https://oliver-zehentleitner.github.io/repo-native-project-memory/" rel="noopener noreferrer"&gt;repo-native project memory&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It is called the repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a repository already remembers
&lt;/h2&gt;

&lt;p&gt;Look at any well-kept project. &lt;code&gt;README.md&lt;/code&gt; says what it is. &lt;code&gt;docs/&lt;/code&gt; says how to use it. &lt;code&gt;tests/&lt;/code&gt; say what it must do. &lt;code&gt;CHANGELOG.md&lt;/code&gt; says what changed. &lt;code&gt;CONTRIBUTING.md&lt;/code&gt; says how a change gets in. &lt;code&gt;pyproject.toml&lt;/code&gt; or &lt;code&gt;package.json&lt;/code&gt; says how it is built. &lt;code&gt;AGENTS.md&lt;/code&gt; says where an agent should look first. Git says who changed what and when.&lt;/p&gt;

&lt;p&gt;That is already a form of project memory: plain files, next to the code, versioned by Git, available in every clone. It has worked for people for decades without a separate platform, and it works for agents for the same reason: anything that can read a directory can read it.&lt;/p&gt;

&lt;p&gt;Agents already consume the README, docs, tests and changelog. Increasingly, &lt;a href="https://blog.technopathy.club/i-let-an-ai-agent-maintain-my-open-source-suite-for-a-week-here-s-what-actually-happened" rel="noopener noreferrer"&gt;they maintain them too&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What the repository usually does not preserve systematically is &lt;a href="https://blog.technopathy.club/keep-the-why-code-becomes-legacy-when-nobody-remembers-why" rel="noopener noreferrer"&gt;why&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The layer that was missing
&lt;/h2&gt;

&lt;p&gt;Why is the retry loop this complicated? Why does that flag exist? Why must these two steps run in this order? Which alternative was tried and dropped?&lt;/p&gt;

&lt;p&gt;That reasoning is produced in every working session, often out loud in the conversation with the agent, and a new session throws it away.&lt;/p&gt;

&lt;p&gt;People have long had mechanisms for the big version of this. Architecture Decision Records work well for the handful of important architectural decisions worth documenting explicitly. Commit messages, issues and pull requests can also preserve pieces of rationale.&lt;/p&gt;

&lt;p&gt;What was expensive was capturing the hundred small reasons that actually make a codebase what it is. Writing each one down manually often cost more than it seemed worth.&lt;/p&gt;

&lt;p&gt;That is the part AI changed.&lt;/p&gt;

&lt;p&gt;Not the need for the why, which is decades old, but the cost of capturing it.&lt;/p&gt;

&lt;p&gt;The reasoning is spoken anyway. An agent that is already part of the conversation can write it down as a byproduct: short, synthesized, in a fixed form, committed with the code it explains.&lt;/p&gt;

&lt;p&gt;Sometimes there is only a reason and no code at all. A change is started, investigated and abandoned once the reason not to make it becomes clear. No resulting diff, commit or PR necessarily records that dead end.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;context/&lt;/code&gt; entry can.&lt;/p&gt;

&lt;p&gt;So the missing layer is one more part of the same repository. It lives where the rest lives, travels with every clone, appears in the pull request next to the code diff, and goes through the same review.&lt;/p&gt;

&lt;p&gt;No account. No daemon. No database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does it change anything?
&lt;/h2&gt;

&lt;p&gt;I measured the abandoned-change case, because that is the one I cared about.&lt;/p&gt;

&lt;p&gt;Twenty fresh agent sessions received the same codebase and the same request: simplify this retry wrapper. Ten had a &lt;code&gt;context/&lt;/code&gt; entry recording why the wrapper looks the way it does and what had already been rejected; ten did not.&lt;/p&gt;

&lt;p&gt;Without the entry, seven of ten offered the already-rejected simplification again.&lt;/p&gt;

&lt;p&gt;With it, all ten found the entry and none did.&lt;/p&gt;

&lt;p&gt;The Markdown file did not make the agent smarter. It gave it one piece of project history the code itself could not provide.&lt;/p&gt;

&lt;p&gt;This also matches a broader lesson from my &lt;a href="https://blog.technopathy.club/same-skill-six-agents-nine-models-what-a-real-eval-matrix-taught-me" rel="noopener noreferrer"&gt;agent and model evaluation matrix&lt;/a&gt;: whether useful context is found and applied depends on the whole agent harness, not only on the underlying model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this is thin
&lt;/h2&gt;

&lt;p&gt;A repository holds knowledge; it becomes useful memory only to the degree that its structure makes that knowledge findable. At hundreds or thousands of entries, that becomes a retrieval problem.&lt;/p&gt;

&lt;p&gt;The layer also stays empty unless something fills it. And &lt;code&gt;context/&lt;/code&gt; is not an established convention, so today an agent has to be told that it exists and when to use it.&lt;/p&gt;

&lt;p&gt;A linter can check whether an entry has the right structure. It cannot check whether the reason is true. A confidently wrong why can be worse than none.&lt;/p&gt;

&lt;p&gt;And much of the why never reaches the repository at all. It remains in chat, tickets, meetings and people's heads.&lt;/p&gt;

&lt;p&gt;This is a documentation discipline with an agent as the writing hand, not a memory subsystem.&lt;/p&gt;

&lt;p&gt;It wins on ownership, review and longevity.&lt;/p&gt;

&lt;p&gt;It loses where nobody reads.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to go from here
&lt;/h2&gt;

&lt;p&gt;The argument, including the model and its limits, is on one page: &lt;a href="https://oliver-zehentleitner.github.io/repo-native-project-memory/" rel="noopener noreferrer"&gt;repo-native project memory&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It is a thesis, not a product, and it would hold for any implementation of the layer.&lt;/p&gt;

&lt;p&gt;The implementation I use is &lt;a href="https://keepthewhy.com" rel="noopener noreferrer"&gt;Keep the Why&lt;/a&gt;: an agent skill and the file convention it maintains, a linter for the structure, and a dashboard to inspect it. The background and motivation are also covered in &lt;a href="https://blog.technopathy.club/keep-the-why-project-memory-for-humans-and-ai-agents" rel="noopener noreferrer"&gt;Keep the Why: Project Memory for Humans and AI Agents&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It is the why layer of repo-native project memory, and it does not claim to be more than that.&lt;/p&gt;

&lt;p&gt;Session memory remembers what happened. Project state remembers where the project is. The why layer preserves why it became what it is.&lt;/p&gt;




&lt;p&gt;I hope you found this informative and useful.&lt;/p&gt;

&lt;p&gt;Follow me on &lt;a href="https://github.com/oliver-zehentleitner" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, &lt;a href="https://bsky.app/profile/o-zehentleitner.bsky.social" rel="noopener noreferrer"&gt;Bluesky&lt;/a&gt;, &lt;a href="https://burningboard.net/@oliverzehentleitner" rel="noopener noreferrer"&gt;Mastodon&lt;/a&gt;, &lt;a href="https://x.com/unicorn_oz" rel="noopener noreferrer"&gt;X&lt;/a&gt;, and &lt;a href="https://www.linkedin.com/in/oliver-zehentleitner/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, or join &lt;a href="https://t.me/unicorndevs" rel="noopener noreferrer"&gt;Telegram&lt;/a&gt; for updates on my latest publications. Constructive feedback is always appreciated.&lt;/p&gt;

&lt;p&gt;Thank you for reading, and happy coding! ¯\_(ツ)_/¯&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>devops</category>
      <category>architecture</category>
    </item>
    <item>
      <title>picows in UNICORN Binance WebSocket API: Up to 2 the Throughput, Opt-In for Now</title>
      <dc:creator>Oliver Zehentleitner</dc:creator>
      <pubDate>Sun, 13 Sep 2026 16:12:30 +0000</pubDate>
      <link>https://dev.to/oliverzehentleitner/picows-in-unicorn-binance-websocket-api-up-to-2x-the-throughput-opt-in-for-now-5c1k</link>
      <guid>https://dev.to/oliverzehentleitner/picows-in-unicorn-binance-websocket-api-up-to-2x-the-throughput-opt-in-for-now-5c1k</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/oliver-zehentleitner/unicorn-binance-websocket-api" rel="noopener noreferrer"&gt;UNICORN Binance WebSocket API&lt;/a&gt; (UBWA) has used &lt;a href="https://github.com/python-websockets/websockets" rel="noopener noreferrer"&gt;&lt;code&gt;websockets&lt;/code&gt;&lt;/a&gt; since day one.&lt;/p&gt;

&lt;p&gt;Since UBWA 2.16.0, you can switch a manager instance to &lt;a href="https://github.com/tarasko/picows" rel="noopener noreferrer"&gt;&lt;code&gt;picows&lt;/code&gt;&lt;/a&gt;, a Cython WebSocket implementation.&lt;/p&gt;

&lt;p&gt;For typical Binance messages below ~1 KB, picows delivers roughly 1.7–2× the throughput with significantly lower CPU cost. At normal trading-bot message rates, however, you probably won't notice a difference.&lt;/p&gt;

&lt;p&gt;The performance is there. The long-term production history isn't — yet. That's why picows is opt-in for now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why picows?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;websockets&lt;/code&gt; is a very good pure-Python library. For a few hundred messages per second it is nowhere near the bottleneck.&lt;/p&gt;

&lt;p&gt;But UBWA is also used for hundreds of &lt;code&gt;depth@100ms&lt;/code&gt; subscriptions, multiplexed connections and local depth caches processing every diff. At those rates, per-message overhead starts to matter.&lt;/p&gt;

&lt;p&gt;picows moves much of the WebSocket protocol handling into Cython.&lt;/p&gt;

&lt;p&gt;It also provides a &lt;code&gt;picows.websockets&lt;/code&gt; compatibility layer with the familiar API: &lt;code&gt;connect()&lt;/code&gt;, &lt;code&gt;recv()&lt;/code&gt;, &lt;code&gt;send()&lt;/code&gt;, &lt;code&gt;close()&lt;/code&gt; and matching exceptions.&lt;/p&gt;

&lt;p&gt;That made the UBWA integration small.&lt;/p&gt;

&lt;h2&gt;
  
  
  Switching libraries
&lt;/h2&gt;

&lt;p&gt;One parameter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;unicorn_binance_websocket_api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BinanceWebSocketApiManager&lt;/span&gt;

&lt;span class="n"&gt;ubwa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BinanceWebSocketApiManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;binance.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ubwa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BinanceWebSocketApiManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;binance.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;websocket_library&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;picows&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;picows is optional:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--upgrade&lt;/span&gt; &lt;span class="s2"&gt;"unicorn-binance-websocket-api[picows]&amp;gt;=2.16.0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Streams, WebSocket API, userData streams, subscribe/unsubscribe, reconnects, signals and proxies use the same UBWA code path.&lt;/p&gt;

&lt;p&gt;There is also no silent fallback.&lt;/p&gt;

&lt;p&gt;If you select &lt;code&gt;"picows"&lt;/code&gt; without installing it, UBWA raises &lt;code&gt;ImportError&lt;/code&gt;. Unknown values raise &lt;code&gt;ValueError&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If a bot says it is running on picows, it should actually be running on picows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benchmark
&lt;/h2&gt;

&lt;p&gt;The benchmark runs both libraries through the full UBWA stack against a local server sending Binance-shaped messages.&lt;/p&gt;

&lt;p&gt;Python 3.13, websockets 16.0, picows 2.1.3, x86_64 Linux, &lt;code&gt;output_default="raw_data"&lt;/code&gt;, median of three runs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;~msg size&lt;/th&gt;
&lt;th&gt;websockets msgs/s&lt;/th&gt;
&lt;th&gt;picows msgs/s&lt;/th&gt;
&lt;th&gt;speedup&lt;/th&gt;
&lt;th&gt;websockets CPU µs/msg&lt;/th&gt;
&lt;th&gt;picows CPU µs/msg&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;aggTrade&lt;/td&gt;
&lt;td&gt;0.2 KB&lt;/td&gt;
&lt;td&gt;201,912&lt;/td&gt;
&lt;td&gt;403,316&lt;/td&gt;
&lt;td&gt;2.00x&lt;/td&gt;
&lt;td&gt;5.1&lt;/td&gt;
&lt;td&gt;2.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;kline&lt;/td&gt;
&lt;td&gt;0.3 KB&lt;/td&gt;
&lt;td&gt;195,460&lt;/td&gt;
&lt;td&gt;371,019&lt;/td&gt;
&lt;td&gt;1.90x&lt;/td&gt;
&lt;td&gt;5.2&lt;/td&gt;
&lt;td&gt;2.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;depth20&lt;/td&gt;
&lt;td&gt;1.0 KB&lt;/td&gt;
&lt;td&gt;153,187&lt;/td&gt;
&lt;td&gt;259,960&lt;/td&gt;
&lt;td&gt;1.70x&lt;/td&gt;
&lt;td&gt;6.8&lt;/td&gt;
&lt;td&gt;4.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;depth diff&lt;/td&gt;
&lt;td&gt;9.1 KB&lt;/td&gt;
&lt;td&gt;64,172&lt;/td&gt;
&lt;td&gt;67,972&lt;/td&gt;
&lt;td&gt;1.06x&lt;/td&gt;
&lt;td&gt;16.3&lt;/td&gt;
&lt;td&gt;15.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;!ticker@arr&lt;/td&gt;
&lt;td&gt;453.9 KB&lt;/td&gt;
&lt;td&gt;1,768&lt;/td&gt;
&lt;td&gt;1,662&lt;/td&gt;
&lt;td&gt;0.94x&lt;/td&gt;
&lt;td&gt;608.7&lt;/td&gt;
&lt;td&gt;641.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;multiplex mix&lt;/td&gt;
&lt;td&gt;0.2 KB&lt;/td&gt;
&lt;td&gt;180,406&lt;/td&gt;
&lt;td&gt;334,188&lt;/td&gt;
&lt;td&gt;1.85x&lt;/td&gt;
&lt;td&gt;5.7&lt;/td&gt;
&lt;td&gt;3.2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The pattern is simple.&lt;/p&gt;

&lt;p&gt;For small Binance messages, picows is clearly faster. Around 10 KB the difference mostly disappears. On the huge 450 KB &lt;code&gt;!ticker@arr&lt;/code&gt; payload, picows even comes out a few percent behind. That row turned out to be an artifact of the benchmark, see the next section.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;output_default="dict"&lt;/code&gt; and JSON parsing included, the advantage for small messages is still around 1.4–1.7×.&lt;/p&gt;

&lt;p&gt;Against live Binance at only a few hundred messages per second, there is effectively no difference. Both libraries spend most of their time waiting for data.&lt;/p&gt;

&lt;p&gt;If that is your workload, switching gives you little.&lt;/p&gt;

&lt;p&gt;If you push tens of thousands of messages per second through one process, it matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  The big-message row is a benchmark artifact
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;!ticker@arr&lt;/code&gt; row is real and reproducible, not noise: a follow-up sweep from 9 KB to 900 KB with ten paired runs per size shows picows 4 to 12 percent behind websockets through UBWA for everything from 32 KB upwards, with a run-to-run spread of only a few percent. It is also an artifact of the benchmark, not of picows. Driven directly, without UBWA, picows wins at every one of those sizes by 1.6x to 2.1x. The difference is how the two libraries drain the socket. The replay server is a firehose on loopback, it never paces, and UBWA's own per-message work on a 450 KB text (a couple of substring scans) is slower than the wire. The kernel receive buffer then autotunes into the megabytes, and picows takes all of it in one recv per loop iteration into a read buffer that keeps doubling: strace counts 79 reads of about 3.4 MB for 600 messages. websockets reads at most 256 KB per recv, about 1060 reads for the same data, and stays cache-friendly. Copying and decoding half-megabyte frames out of a multi-megabyte buffer that has already left the cache costs more per byte than picows saves on parsing. The proof is a one-line change: capping &lt;code&gt;SO_RCVBUF&lt;/code&gt; to 128 KB on the client socket flips the 450 KB result to picows 1.3x to 1.4x ahead, with nothing else touched. The benchmark script has a &lt;code&gt;--rcvbuf&lt;/code&gt; option for that now, and the full tables are in &lt;a href="https://github.com/oliver-zehentleitner/unicorn-binance-websocket-api/blob/master/context/websocket-library.md" rel="noopener noreferrer"&gt;context/websocket-library.md&lt;/a&gt;. On a real Binance connection the socket buffer never fills like that, a WAN link delivers a few megabytes per second at most and the consumer keeps up, which is also what the 24 h soak showed: picows with less CPU and less memory. Thanks to Taras Kozlov, the picows author, for asking the question that led to this.&lt;/p&gt;

&lt;h2&gt;
  
  
  The benchmark found a UBWA bottleneck first
&lt;/h2&gt;

&lt;p&gt;The first benchmark showed only about 1.4× improvement.&lt;/p&gt;

&lt;p&gt;Profiling found why.&lt;/p&gt;

&lt;p&gt;UBWA had 18 &lt;code&gt;logger.debug()&lt;/code&gt; f-strings formatted for every message even when debug logging was disabled, seven lock acquisitions per message where one was enough, and heartbeat/stop checks running twice.&lt;/p&gt;

&lt;p&gt;After removing that overhead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;websockets: &lt;strong&gt;116k → 202k msgs/s&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;picows: &lt;strong&gt;163k → 403k msgs/s&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;picows did not suddenly get faster.&lt;/p&gt;

&lt;p&gt;UBWA stopped hiding its speed.&lt;/p&gt;

&lt;p&gt;The details are documented in &lt;a href="https://github.com/oliver-zehentleitner/unicorn-binance-websocket-api/blob/master/context/stream-loop.md" rel="noopener noreferrer"&gt;&lt;code&gt;context/stream-loop.md&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I did not use the native picows API
&lt;/h2&gt;

&lt;p&gt;picows also has a native listener API based on &lt;code&gt;ws_connect()&lt;/code&gt; and &lt;code&gt;WSListener&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I benchmarked it too.&lt;/p&gt;

&lt;p&gt;On 0.2 KB messages it managed roughly 499k msgs/s versus 485k through the compatibility API. On 9 KB messages the difference was around 13%.&lt;/p&gt;

&lt;p&gt;Inside the full UBWA stack that would translate to less than 5% end-to-end.&lt;/p&gt;

&lt;p&gt;In return, UBWA would need a second connection implementation of roughly 300–400 lines.&lt;/p&gt;

&lt;p&gt;Not worth it.&lt;/p&gt;

&lt;p&gt;UBWA stays on the compatibility API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure-path testing found a real bug
&lt;/h2&gt;

&lt;p&gt;Performance benchmarks are easy. Failure handling is more interesting.&lt;/p&gt;

&lt;p&gt;The test suite now runs both libraries through scenarios including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;server-side closes and reconnects&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;fragmented frames&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;450 KB payloads&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;messages above &lt;code&gt;max_size&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;server pings&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Unicode&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;rejected handshakes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;WebSocket API round trips&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;keepalive timeouts&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rejected-handshake test found a real compatibility issue.&lt;/p&gt;

&lt;p&gt;When Binance returns HTTP 429 or 404 during the WebSocket upgrade, UBWA reads the status from &lt;code&gt;InvalidStatus&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;websockets&lt;/code&gt; exposed &lt;code&gt;response.status_code&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;picows 2.1.x exposed &lt;code&gt;response.status&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;UBWA hit an &lt;code&gt;AttributeError&lt;/code&gt;, the stream thread died and nothing useful was logged.&lt;/p&gt;

&lt;p&gt;I reported it upstream as &lt;a href="https://github.com/tarasko/picows/issues/108" rel="noopener noreferrer"&gt;tarasko/picows#108&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;picows 2.2.0 fixed it the next day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proxy support got simpler too
&lt;/h2&gt;

&lt;p&gt;picows 2.3.0 added native HTTP, HTTPS, SOCKS4 and SOCKS5 proxy support.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;websockets&lt;/code&gt; has supported the same since 15.0.&lt;/p&gt;

&lt;p&gt;UBWA previously handled SOCKS5 itself using PySocks and a blocking handshake inside the event loop.&lt;/p&gt;

&lt;p&gt;That code is now gone.&lt;/p&gt;

&lt;p&gt;UBWA simply passes the proxy URL to the selected WebSocket library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;ubwa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BinanceWebSocketApiManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;binance.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;proxy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;socks5://user:pass@127.0.0.1:9050&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ubwa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BinanceWebSocketApiManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;binance.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;proxy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://127.0.0.1:3128&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both libraries now support:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://
https://
socks4://
socks5://
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The old &lt;code&gt;socks5_proxy_server&lt;/code&gt; parameters still work and are converted internally.&lt;/p&gt;

&lt;p&gt;Testing also found two UBWA issues in the old proxy path:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;rejected SOCKS5 credentials could kill a stream thread without a useful log message&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;TLS certificate verification was not actually enabled on the proxy path despite the option defaulting to &lt;code&gt;True&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both are fixed.&lt;/p&gt;

&lt;p&gt;One difference remains: &lt;code&gt;websockets&lt;/code&gt; currently does not URL-decode proxy credentials such as &lt;code&gt;p%40ss&lt;/code&gt;, while python-socks/picows does. I reported that as &lt;a href="https://github.com/python-websockets/websockets/issues/1761" rel="noopener noreferrer"&gt;python-websockets/websockets#1761&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;UBWA rejects affected credentials up front when using &lt;code&gt;websockets&lt;/code&gt; instead of entering a reconnect loop.&lt;/p&gt;

&lt;p&gt;REST requests used for listenKey handling still follow SOCKS5 proxies only; HTTP(S) proxy support there is tracked in &lt;a href="https://github.com/oliver-zehentleitner/unicorn-binance-rest-api/issues/139" rel="noopener noreferrer"&gt;unicorn-binance-rest-api#139&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The performance and soak tests below were run with picows 2.1.3. During integration, 2.2.0 fixed the handshake compatibility issue and 2.3.0 added native proxy support. That is why UBWA requires picows 2.3.0.&lt;/p&gt;

&lt;h2&gt;
  
  
  24 hours against live Binance
&lt;/h2&gt;

&lt;p&gt;Local tests do not tell you what happens after hours of reconnects, traffic spikes and memory allocation.&lt;/p&gt;

&lt;p&gt;So both libraries ran for 24 hours in parallel against &lt;code&gt;binance.com&lt;/code&gt; with identical subscriptions.&lt;/p&gt;

&lt;p&gt;Load:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;!ticker@arr&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;!miniTicker@arr&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;aggTrade&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;trade&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;depth20@100ms&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;kline_1m&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;bookTicker&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;depth@100ms&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;across up to 50 USDT markets.&lt;/p&gt;

&lt;p&gt;Host: 8 cores, 12 GB RAM, Python 3.13.5.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;picows&lt;/th&gt;
&lt;th&gt;websockets&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Messages / data&lt;/td&gt;
&lt;td&gt;138.8 M / 49.9 GB&lt;/td&gt;
&lt;td&gt;137.9 M / 49.7 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avg / peak msgs/s&lt;/td&gt;
&lt;td&gt;1,606 / 8,260&lt;/td&gt;
&lt;td&gt;1,596 / 7,695&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RSS start → end&lt;/td&gt;
&lt;td&gt;62 → 126 MB&lt;/td&gt;
&lt;td&gt;63 → 149 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPU avg&lt;/td&gt;
&lt;td&gt;9.5 %&lt;/td&gt;
&lt;td&gt;12.8 %&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reconnects (arr / markets / depth)&lt;/td&gt;
&lt;td&gt;2 / 80 / 2&lt;/td&gt;
&lt;td&gt;2 / 88 / 2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reconnect duration&lt;/td&gt;
&lt;td&gt;5–6 s&lt;/td&gt;
&lt;td&gt;5–6 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Max seconds without data&lt;/td&gt;
&lt;td&gt;5 s&lt;/td&gt;
&lt;td&gt;5 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Errors / stalls / unrepairable streams&lt;/td&gt;
&lt;td&gt;0 / 0 / 0&lt;/td&gt;
&lt;td&gt;0 / 0 / 0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;138 million messages later: no picows-specific failure, about 26% lower average CPU usage and 23 MB less RSS at the end of the run.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The high reconnect count on one stream occurred almost entirely during a four-hour high-load window.&lt;/p&gt;

&lt;p&gt;Both implementations disconnected in the same seconds with keepalive ping timeouts and recovered within five to six seconds.&lt;/p&gt;

&lt;p&gt;That strongly points to a shared external or workload-related cause rather than either WebSocket implementation.&lt;/p&gt;

&lt;p&gt;Memory rose during traffic peaks and then remained flat for the final hours despite further reconnects.&lt;/p&gt;

&lt;p&gt;No sign of a reconnect leak.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why websockets is still the default
&lt;/h2&gt;

&lt;p&gt;Because 138 million messages and a 24-hour soak are good evidence.&lt;/p&gt;

&lt;p&gt;They are not the same as thousands of users running picows for months on different systems.&lt;/p&gt;

&lt;p&gt;The compatibility layer is younger than &lt;code&gt;websockets&lt;/code&gt;, and issue #108 already showed that small API differences can matter.&lt;/p&gt;

&lt;p&gt;So for now:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;websockets remains the default. picows is opt-in.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If picows holds up across enough real-world setups, the default can flip later.&lt;/p&gt;

&lt;p&gt;The switch will remain either way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--upgrade&lt;/span&gt; &lt;span class="s2"&gt;"unicorn-binance-websocket-api[picows]&amp;gt;=2.16.0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;unicorn_binance_websocket_api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BinanceWebSocketApiManager&lt;/span&gt;

&lt;span class="n"&gt;ubwa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BinanceWebSocketApiManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;binance.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;websocket_library&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;picows&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ubwa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aggTrade&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;depth20@100ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;btcusdt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ethusdt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;solusdt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run your workload with both libraries and compare CPU and memory usage.&lt;/p&gt;

&lt;p&gt;If you have numbers, edge cases or failures, post them in &lt;a href="https://github.com/oliver-zehentleitner/unicorn-binance-websocket-api/issues/477" rel="noopener noreferrer"&gt;issue #477&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That feedback will decide whether picows becomes the default.&lt;/p&gt;

&lt;p&gt;The reasoning behind these decisions — including the native picows implementation I chose not to build — lives next to the code in &lt;code&gt;context/&lt;/code&gt;, maintained with &lt;a href="https://keepthewhy.com/" rel="noopener noreferrer"&gt;Keep the Why&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;I hope you found this informative and useful.&lt;/p&gt;

&lt;p&gt;Follow me on &lt;a href="https://github.com/oliver-zehentleitner" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, &lt;a href="https://bsky.app/profile/o-zehentleitner.bsky.social" rel="noopener noreferrer"&gt;Bluesky&lt;/a&gt;, &lt;a href="https://burningboard.net/@oliverzehentleitner" rel="noopener noreferrer"&gt;Mastodon&lt;/a&gt;, &lt;a href="https://x.com/unicorn_oz" rel="noopener noreferrer"&gt;X&lt;/a&gt;, and &lt;a href="https://www.linkedin.com/in/oliver-zehentleitner/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, or join &lt;a href="https://t.me/unicorndevs" rel="noopener noreferrer"&gt;Telegram&lt;/a&gt; for updates on my latest publications. Constructive feedback is always appreciated.&lt;/p&gt;

&lt;p&gt;Thank you for reading, and happy coding! ¯\_(ツ)_/¯&lt;/p&gt;

</description>
      <category>python</category>
      <category>websocket</category>
      <category>asyncio</category>
      <category>algotrading</category>
    </item>
    <item>
      <title>One Index, Many Writers: Avoiding Git Merge Conflicts with Deterministic Write Areas</title>
      <dc:creator>Oliver Zehentleitner</dc:creator>
      <pubDate>Wed, 09 Sep 2026 18:37:10 +0000</pubDate>
      <link>https://dev.to/oliverzehentleitner/one-index-many-writers-avoiding-git-merge-conflicts-with-deterministic-write-areas-4718</link>
      <guid>https://dev.to/oliverzehentleitner/one-index-many-writers-avoiding-git-merge-conflicts-with-deterministic-write-areas-4718</guid>
      <description>&lt;p&gt;While working on Keep the Why, I ran into a very ordinary Git problem.&lt;/p&gt;

&lt;p&gt;There is one shared &lt;code&gt;context/index.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Developers and coding agents can add new context files in different branches. The changes are unrelated, but they are not written back to the repository at the same time. Git has to merge them later.&lt;/p&gt;

&lt;p&gt;That is where things get interesting.&lt;/p&gt;

&lt;p&gt;Imagine two branches.&lt;/p&gt;

&lt;p&gt;One adds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;billing.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another adds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;caching.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both also update &lt;code&gt;context/index.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If new entries are simply appended to the end of the file, both branches modify the same place.&lt;/p&gt;

&lt;p&gt;Git sees overlapping changes.&lt;/p&gt;

&lt;p&gt;Merge conflict.&lt;/p&gt;

&lt;p&gt;The conflict is not semantic. Nobody disagrees about the content. It only exists because several writers use the same file and their changes are integrated later.&lt;/p&gt;

&lt;p&gt;This came up in &lt;a href="https://github.com/oliver-zehentleitner/keep-the-why/issues/194" rel="noopener noreferrer"&gt;Keep the Why issue #194&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alphabetical sorting helps, but not enough
&lt;/h2&gt;

&lt;p&gt;My first thought was simple: stop appending new entries and keep the index alphabetically sorted.&lt;/p&gt;

&lt;p&gt;That already spreads changes across the file.&lt;/p&gt;

&lt;p&gt;But especially with a small index, it does not solve the problem.&lt;/p&gt;

&lt;p&gt;Suppose the index contains only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;architecture
deployment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One branch adds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;billing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another adds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;caching
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both additions belong between the same two existing lines.&lt;/p&gt;

&lt;p&gt;So even though the entries are different and correctly sorted, Git can still see both branches changing the same area.&lt;/p&gt;

&lt;p&gt;The interesting part is that this problem is actually worse while the index is still small.&lt;/p&gt;

&lt;p&gt;With only a few existing entries, there are only a few natural places where Git can anchor an insertion. New entries therefore have a relatively high chance of landing in the same gap.&lt;/p&gt;

&lt;p&gt;As the index grows, this usually improves by itself.&lt;/p&gt;

&lt;p&gt;More existing entries create more separation points:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;architecture
billing
caching
deployment
logging
monitoring
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A new entry is now much more likely to land in its own area.&lt;/p&gt;

&lt;p&gt;So the main problem is not a large index.&lt;/p&gt;

&lt;p&gt;It is a &lt;strong&gt;sparse index with several delayed writers&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the structure before it is needed
&lt;/h2&gt;

&lt;p&gt;The solution I implemented is simple.&lt;/p&gt;

&lt;p&gt;Every new index starts with fixed sections:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## 0&lt;/span&gt;
&lt;span class="gu"&gt;## 1&lt;/span&gt;
&lt;span class="gu"&gt;## 2&lt;/span&gt;
...
&lt;span class="gu"&gt;## 9&lt;/span&gt;

&lt;span class="gu"&gt;## A&lt;/span&gt;
&lt;span class="gu"&gt;## B&lt;/span&gt;
&lt;span class="gu"&gt;## C&lt;/span&gt;
...
&lt;span class="gu"&gt;## Z&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All 36 sections exist from the beginning, even when they are empty.&lt;/p&gt;

&lt;p&gt;A topic is inserted below the section matching the first character of its filename.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;billing.md     -&amp;gt; B
caching.md     -&amp;gt; C
deployment.md  -&amp;gt; D
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is not really the alphabet.&lt;/p&gt;

&lt;p&gt;The important part is that the write areas already exist before concurrent branches need them.&lt;/p&gt;

&lt;p&gt;Instead of every new entry competing for one append position, changes are distributed across predefined parts of the file.&lt;/p&gt;

&lt;p&gt;The headings become stable merge anchors.&lt;/p&gt;

&lt;p&gt;In other words, the index gets some structure early instead of waiting for the content itself to create enough structure later.&lt;/p&gt;

&lt;h2&gt;
  
  
  A useful side effect for coding agents
&lt;/h2&gt;

&lt;p&gt;The original problem came from Git.&lt;/p&gt;

&lt;p&gt;But the fixed structure also makes the index nicer for agents.&lt;/p&gt;

&lt;p&gt;An agent does not have to treat &lt;code&gt;index.md&lt;/code&gt; as one unstructured Markdown block. It can search predictable sections and narrow the retrieval area before reading more context.&lt;/p&gt;

&lt;p&gt;So the same structure gives two benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Git gets stable places for independent changes to land.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Coding agents get predictable places to search.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That was not the original reason for the change, but it fits the way Keep the Why is supposed to work: simple, deterministic retrieval first, deeper reading only when necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I like about this solution
&lt;/h2&gt;

&lt;p&gt;There is no database.&lt;/p&gt;

&lt;p&gt;No locking.&lt;/p&gt;

&lt;p&gt;No generated index.&lt;/p&gt;

&lt;p&gt;No merge driver.&lt;/p&gt;

&lt;p&gt;No additional service.&lt;/p&gt;

&lt;p&gt;Just a little bit of structure added before it is actually needed.&lt;/p&gt;

&lt;p&gt;The funny part is that once the index becomes larger, the entries themselves increasingly provide the separation Git needs. The fixed &lt;code&gt;0-9&lt;/code&gt; and &lt;code&gt;A-Z&lt;/code&gt; sections are mostly there to make the early and sparse state behave better.&lt;/p&gt;

&lt;p&gt;It is a small change, but I like the general lesson behind it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When many independent writers modify one Git-managed file, avoiding conflicts can be less about smarter merging and more about designing where changes are allowed to land.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;I hope you found this informative and useful.&lt;/p&gt;

&lt;p&gt;Follow me on &lt;a href="https://github.com/oliver-zehentleitner" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, &lt;a href="https://bsky.app/profile/o-zehentleitner.bsky.social" rel="noopener noreferrer"&gt;Bluesky&lt;/a&gt;, &lt;a href="https://burningboard.net/@oliverzehentleitner" rel="noopener noreferrer"&gt;Mastodon&lt;/a&gt;, &lt;a href="https://x.com/unicorn_oz" rel="noopener noreferrer"&gt;X&lt;/a&gt;, and &lt;a href="https://www.linkedin.com/in/oliver-zehentleitner/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, or join &lt;a href="https://t.me/unicorndevs" rel="noopener noreferrer"&gt;Telegram&lt;/a&gt; for updates on my latest publications. Constructive feedback is always appreciated.&lt;/p&gt;

&lt;p&gt;Thank you for reading, and happy coding! ¯\_(ツ)_/¯&lt;/p&gt;

</description>
    </item>
    <item>
      <title>What happens when a coding agent forgets why a change was rejected?</title>
      <dc:creator>Oliver Zehentleitner</dc:creator>
      <pubDate>Tue, 08 Sep 2026 14:16:02 +0000</pubDate>
      <link>https://dev.to/oliverzehentleitner/what-happens-when-a-coding-agent-forgets-why-a-change-was-rejected-327a</link>
      <guid>https://dev.to/oliverzehentleitner/what-happens-when-a-coding-agent-forgets-why-a-change-was-rejected-327a</guid>
      <description>&lt;p&gt;Coding agents are pretty good at understanding code.&lt;/p&gt;

&lt;p&gt;But a fresh session usually does not know which ideas were already investigated, tested and rejected. If the rejected idea left no code change behind, Git has almost nothing to show.&lt;/p&gt;

&lt;p&gt;So I wanted to see what difference a tiny piece of project memory actually makes.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small test
&lt;/h2&gt;

&lt;p&gt;I built a tiny repository around a retry wrapper for a payment gateway.&lt;/p&gt;

&lt;p&gt;The code handles &lt;code&gt;429&lt;/code&gt; responses using the gateway's &lt;code&gt;Retry-After&lt;/code&gt; header, plus exponential backoff and jitter.&lt;/p&gt;

&lt;p&gt;It looks a bit more complicated than a plain retry loop, so it is exactly the kind of code an agent might want to simplify.&lt;/p&gt;

&lt;p&gt;The important part: a simpler version had already been considered and rejected because retrying before the limiter reset caused more &lt;code&gt;429&lt;/code&gt; responses under load.&lt;/p&gt;

&lt;p&gt;Then I created two copies of the repository.&lt;/p&gt;

&lt;p&gt;In the first one, there was no explanation.&lt;/p&gt;

&lt;p&gt;In the second one, I added a small Markdown file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;context/retries.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It explained the rate-limiter behavior and said that replacing the wrapper with a plain retry loop had already been tried and rejected.&lt;/p&gt;

&lt;p&gt;Everything else was identical.&lt;/p&gt;

&lt;p&gt;For these runs, Keep the Why was installed project-scoped and loaded in all 20 sessions through Claude Code's &lt;code&gt;SessionStart&lt;/code&gt; hook. The skill then uses the repository's &lt;code&gt;context/index.md&lt;/code&gt; to find relevant project context such as &lt;code&gt;context/retries.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then I started ten fresh Claude Code sessions against each repository and gave them the same prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This retry wrapper looks over-engineered. A plain retry loop would do the same thing. Simplify it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The experiment uses the same isolated fixture machinery as the public &lt;a href="https://github.com/oliver-zehentleitner/keep-the-why/tree/main/tools/evals" rel="noopener noreferrer"&gt;Keep the Why eval runner&lt;/a&gt;. The &lt;a href="https://github.com/oliver-zehentleitner/keep-the-why/tree/main/experiments/rejected-change/results" rel="noopener noreferrer"&gt;raw transcripts, diffs and per-run grades&lt;/a&gt; are published as well, so the numbers below can be checked instead of taken on faith.&lt;/p&gt;

&lt;h2&gt;
  
  
  The interesting result
&lt;/h2&gt;

&lt;p&gt;I expected at least one control run to break the retry behavior.&lt;/p&gt;

&lt;p&gt;That did not happen.&lt;/p&gt;

&lt;p&gt;The agent was more careful than that.&lt;/p&gt;

&lt;p&gt;All ten control sessions noticed that &lt;code&gt;Retry-After&lt;/code&gt; mattered. The few that changed the function preserved the important behavior.&lt;/p&gt;

&lt;p&gt;But they still had a problem:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;they had no way to know that the simpler approach had already been investigated.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Seven of the ten control sessions ended up offering the rejected simplification as a valid option again.&lt;/p&gt;

&lt;p&gt;That makes sense from the agent's perspective. The repository contained no evidence that anyone had already tried it.&lt;/p&gt;

&lt;p&gt;With the context file, the behavior changed completely.&lt;/p&gt;

&lt;p&gt;All ten sessions found the rationale.&lt;/p&gt;

&lt;p&gt;All ten understood that the requested simplification had already been rejected.&lt;/p&gt;

&lt;p&gt;None of them put the bad idea back on the table.&lt;/p&gt;

&lt;p&gt;The treatment runs were also noticeably faster: the median wall time dropped from 43 seconds in the control group to 18 seconds with the recorded rationale.&lt;/p&gt;

&lt;p&gt;I would not generalize that number from such a small experiment, but the reason is straightforward: without the context, the agent first had to reconstruct the decision from the code. With it, the session could start where the previous one had ended.&lt;/p&gt;

&lt;p&gt;That was the interesting part for me.&lt;/p&gt;

&lt;p&gt;The Markdown file did not make the agent smarter.&lt;/p&gt;

&lt;p&gt;It simply gave it one piece of project history that the code itself could not provide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code is not the same as decision history
&lt;/h2&gt;

&lt;p&gt;The control sessions were actually pretty good.&lt;/p&gt;

&lt;p&gt;They inspected the implementation and understood why &lt;code&gt;Retry-After&lt;/code&gt;, backoff and jitter might matter.&lt;/p&gt;

&lt;p&gt;Several even noticed that the repository did not explain whether the complexity was intentional.&lt;/p&gt;

&lt;p&gt;And that is the important distinction.&lt;/p&gt;

&lt;p&gt;Code can often tell you &lt;strong&gt;what&lt;/strong&gt; is happening.&lt;/p&gt;

&lt;p&gt;Sometimes it even lets you guess &lt;strong&gt;why&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But guessing the reason is not the same as knowing that somebody already investigated an alternative and rejected it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rejected changes are project knowledge too
&lt;/h2&gt;

&lt;p&gt;Some very useful engineering knowledge comes from things that never made it into the code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a refactor that broke an external integration;&lt;/li&gt;
&lt;li&gt;a dependency that looked replaceable but was not;&lt;/li&gt;
&lt;li&gt;an optimization that made performance worse;&lt;/li&gt;
&lt;li&gt;a workaround that should not be "cleaned up";&lt;/li&gt;
&lt;li&gt;a migration approach that was investigated and abandoned.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the final result is "leave the code as it is", Git has very little to record.&lt;/p&gt;

&lt;p&gt;Humans often keep that knowledge in their heads.&lt;/p&gt;

&lt;p&gt;A fresh coding-agent session does not have that context.&lt;/p&gt;

&lt;h2&gt;
  
  
  This is the idea behind Keep the Why
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://keepthewhy.com/" rel="noopener noreferrer"&gt;Keep the Why&lt;/a&gt; keeps this kind of reasoning directly in the repository.&lt;/p&gt;

&lt;p&gt;The format is intentionally boring:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;context/
├── index.md
├── architecture.md
├── retries.md
└── deployment.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plain Markdown. Versioned with Git.&lt;/p&gt;

&lt;p&gt;No database. No daemon. No RAG system.&lt;/p&gt;

&lt;p&gt;Humans can read it, coding agents can read it, and the reasoning travels with the repository.&lt;/p&gt;

&lt;p&gt;For me, this is less about "AI memory" and more about &lt;strong&gt;project memory&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If we tried something and rejected it, that is useful project knowledge.&lt;/p&gt;

&lt;p&gt;If strange code exists because production behaved in a surprising way, that is useful project knowledge too.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I took away from it
&lt;/h2&gt;

&lt;p&gt;This was a small experiment: twenty runs, one function and one model family.&lt;/p&gt;

&lt;p&gt;So I would not turn the numbers into some universal benchmark.&lt;/p&gt;

&lt;p&gt;But the behavior was clear.&lt;/p&gt;

&lt;p&gt;Without recorded rationale, the agent had to rediscover the same decision.&lt;/p&gt;

&lt;p&gt;With it, the next session could start where the previous one ended.&lt;/p&gt;

&lt;p&gt;That is basically the whole idea:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't make the next developer — human or AI — rediscover the same dead ends.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sometimes the most important thing to preserve is not what changed.&lt;/p&gt;

&lt;p&gt;It is why nothing changed.&lt;/p&gt;




&lt;p&gt;Keep the Why is open source:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/oliver-zehentleitner/keep-the-why" rel="noopener noreferrer"&gt;https://github.com/oliver-zehentleitner/keep-the-why&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://keepthewhy.com" rel="noopener noreferrer"&gt;https://keepthewhy.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;I hope you found this informative and useful.&lt;/p&gt;

&lt;p&gt;Follow me on &lt;a href="https://github.com/oliver-zehentleitner" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, &lt;a href="https://bsky.app/profile/o-zehentleitner.bsky.social" rel="noopener noreferrer"&gt;Bluesky&lt;/a&gt;, &lt;a href="https://burningboard.net/@oliverzehentleitner" rel="noopener noreferrer"&gt;Mastodon&lt;/a&gt;, &lt;a href="https://x.com/unicorn_oz" rel="noopener noreferrer"&gt;X&lt;/a&gt;, and &lt;a href="https://www.linkedin.com/in/oliver-zehentleitner/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, or join &lt;a href="https://t.me/unicorndevs" rel="noopener noreferrer"&gt;Telegram&lt;/a&gt; for updates on my latest publications. Constructive feedback is always appreciated.&lt;/p&gt;

&lt;p&gt;Thank you for reading, and happy coding! ¯\_(ツ)_/¯&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>devtools</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Same Skill, Six Agents, Nine Models: What a Real Eval Matrix Taught Me</title>
      <dc:creator>Oliver Zehentleitner</dc:creator>
      <pubDate>Mon, 24 Aug 2026 15:05:22 +0000</pubDate>
      <link>https://dev.to/oliverzehentleitner/same-skill-six-agents-nine-models-what-a-real-eval-matrix-taught-me-4i21</link>
      <guid>https://dev.to/oliverzehentleitner/same-skill-six-agents-nine-models-what-a-real-eval-matrix-taught-me-4i21</guid>
      <description>&lt;h2&gt;
  
  
  I ran the same skill and the same task through different coding agents. The model mattered. But the agent harness changed the outcome far more than I expected.
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;I tested &lt;a href="https://keepthewhy.com/" rel="noopener noreferrer"&gt;Keep the Why&lt;/a&gt;, my open-source agent skill for preserving the reasoning behind a codebase, through six coding agents, with a matrix spanning nine hosted models plus a local Ollama run.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The model mattered. But one result stood out much more than I expected: the same model can behave very differently depending on the agent around it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Publishing an agent skill and saying "works great in my coding agent" is not much of a claim.&lt;/p&gt;

&lt;p&gt;It is an anecdote.&lt;/p&gt;

&lt;p&gt;Keep the Why is built on the open Agent Skills format. It is deliberately not tied to one model or one coding agent.&lt;/p&gt;

&lt;p&gt;So eventually I had to answer a more interesting question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens if I keep the skill and task constant, but change the model and the agent around it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I built matrix support into the Keep the Why eval tooling and started running it.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://keepthewhy.com/agent-matrix/" rel="noopener noreferrer"&gt;Agent &amp;amp; Model Matrix&lt;/a&gt; contains real runs across six tested coding agents: Cline, Codex CLI, Kimi Code, opencode, Pi, and Claude Code. The model set spans Anthropic, DeepSeek, Google, OpenAI, xAI, Mistral, Moonshot, Z.ai, and Qwen, plus a local Ollama run.&lt;/p&gt;

&lt;p&gt;I expected meaningful differences between models.&lt;/p&gt;

&lt;p&gt;What I did not expect was how much the &lt;strong&gt;agent harness itself&lt;/strong&gt; would matter.&lt;/p&gt;

&lt;p&gt;One terminology note before going further:&lt;/p&gt;

&lt;p&gt;When I say &lt;strong&gt;agent harness&lt;/strong&gt;, I mean the coding agent and its surrounding scaffolding.&lt;/p&gt;

&lt;p&gt;When I say &lt;strong&gt;eval harness&lt;/strong&gt;, I mean my own runner, fixtures, isolation, capture, and judging infrastructure.&lt;/p&gt;

&lt;p&gt;Those are two different things.&lt;/p&gt;

&lt;p&gt;The main finding in this article is about the first one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The model is not the agent
&lt;/h2&gt;

&lt;p&gt;We often talk about coding models as if the model determines the behavior.&lt;/p&gt;

&lt;p&gt;GPT did this.&lt;/p&gt;

&lt;p&gt;Gemini failed that.&lt;/p&gt;

&lt;p&gt;Grok followed the instruction.&lt;/p&gt;

&lt;p&gt;But that is usually not the actual system executing our code.&lt;/p&gt;

&lt;p&gt;The model lives inside an agent.&lt;/p&gt;

&lt;p&gt;That agent decides how instructions are assembled, which context gets loaded, which tools exist, how tool results are presented back to the model, how permissions work, how planning is handled, when to ask, and when to act.&lt;/p&gt;

&lt;p&gt;That surrounding machinery is often called the harness or agentic scaffolding.&lt;/p&gt;

&lt;p&gt;And in my matrix, changing it could completely change the outcome.&lt;/p&gt;

&lt;p&gt;Same model.&lt;/p&gt;

&lt;p&gt;Same skill.&lt;/p&gt;

&lt;p&gt;Same task.&lt;/p&gt;

&lt;p&gt;Different agent.&lt;/p&gt;

&lt;p&gt;Different behavior.&lt;/p&gt;

&lt;p&gt;That is probably the most important thing this experiment taught me.&lt;/p&gt;

&lt;h2&gt;
  
  
  A case designed to expose exactly that
&lt;/h2&gt;

&lt;p&gt;The cross-agent matrix currently uses one representative Keep the Why eval case called &lt;code&gt;chestertons-fence-guard&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It deliberately gives the agent some strange-looking code.&lt;/p&gt;

&lt;p&gt;The code appears unnecessary.&lt;/p&gt;

&lt;p&gt;There is no explanation next to it.&lt;/p&gt;

&lt;p&gt;A coding agent optimized for aggressively cleaning things up has an obvious temptation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This looks redundant. Remove it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But that is exactly what it should not do.&lt;/p&gt;

&lt;p&gt;The expected behavior is based on Chesterton's Fence.&lt;/p&gt;

&lt;p&gt;Before removing something you do not understand, first find out why it exists.&lt;/p&gt;

&lt;p&gt;For Keep the Why that means checking the repository context, inspecting git history, looking for documented rationale and, if none can be found, flagging the uncertainty and asking before removing the code.&lt;/p&gt;

&lt;p&gt;It is a useful test because success is not about whether the model can read Python.&lt;/p&gt;

&lt;p&gt;Almost every model can understand the code.&lt;/p&gt;

&lt;p&gt;The interesting question is what the agent &lt;strong&gt;does next&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gemini 3.1 Pro: one model, different harness, completely different result
&lt;/h2&gt;

&lt;p&gt;Take Gemini 3.1 Pro through OpenRouter.&lt;/p&gt;

&lt;p&gt;In the matrix snapshot used for this article:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cline&lt;/td&gt;
&lt;td&gt;Pass - 10/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codex CLI&lt;/td&gt;
&lt;td&gt;Fail - 2/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kimi Code&lt;/td&gt;
&lt;td&gt;Fail - 0/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;opencode&lt;/td&gt;
&lt;td&gt;Fail - 0/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pi&lt;/td&gt;
&lt;td&gt;Fail - 2/10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That is not a subtle difference.&lt;/p&gt;

&lt;p&gt;Under Cline, the run fully matched the expected behavior.&lt;/p&gt;

&lt;p&gt;The same model through Kimi Code or opencode received a zero from the same judging setup.&lt;/p&gt;

&lt;p&gt;Nothing about the underlying Gemini model changed.&lt;/p&gt;

&lt;p&gt;The task did not change.&lt;/p&gt;

&lt;p&gt;The Keep the Why skill did not change.&lt;/p&gt;

&lt;p&gt;The agent harness did.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kimi K3 shows the same effect
&lt;/h2&gt;

&lt;p&gt;Kimi K3 is even more interesting:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cline&lt;/td&gt;
&lt;td&gt;Pass - 10/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codex CLI&lt;/td&gt;
&lt;td&gt;Pass - 10/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kimi Code&lt;/td&gt;
&lt;td&gt;Fail - 3/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;opencode&lt;/td&gt;
&lt;td&gt;Fail - 2/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pi&lt;/td&gt;
&lt;td&gt;Pass - 10/10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The same model scored perfectly in several harnesses and poorly in others.&lt;/p&gt;

&lt;p&gt;That sounds almost absurd when you reduce the system to the name of its model.&lt;/p&gt;

&lt;p&gt;It makes much more sense once you stop doing that.&lt;/p&gt;

&lt;p&gt;The model is one component.&lt;/p&gt;

&lt;p&gt;The agent using it is another.&lt;/p&gt;

&lt;h2&gt;
  
  
  And GLM-5.3 does it again
&lt;/h2&gt;

&lt;p&gt;GLM-5.3:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cline&lt;/td&gt;
&lt;td&gt;Pass - 10/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codex CLI&lt;/td&gt;
&lt;td&gt;Fail - 1/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kimi Code&lt;/td&gt;
&lt;td&gt;Fail - 3/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;opencode&lt;/td&gt;
&lt;td&gt;Pass - 9/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pi&lt;/td&gt;
&lt;td&gt;Pass - 9/10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Again, there is no useful single answer to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How good is GLM-5.3 at this task?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A better question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How good is GLM-5.3 at this task inside which agent?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a much less convenient benchmark question.&lt;/p&gt;

&lt;p&gt;But it is closer to the system we actually run.&lt;/p&gt;

&lt;h2&gt;
  
  
  The agent harness is not plumbing
&lt;/h2&gt;

&lt;p&gt;This changed how I think about coding-agent benchmarks.&lt;/p&gt;

&lt;p&gt;The harness around a model can control or influence:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;system and developer instructions&lt;/li&gt;
&lt;li&gt;skill loading&lt;/li&gt;
&lt;li&gt;context construction&lt;/li&gt;
&lt;li&gt;repository discovery&lt;/li&gt;
&lt;li&gt;tool definitions&lt;/li&gt;
&lt;li&gt;tool-call feedback&lt;/li&gt;
&lt;li&gt;planning behavior&lt;/li&gt;
&lt;li&gt;permission handling&lt;/li&gt;
&lt;li&gt;retries&lt;/li&gt;
&lt;li&gt;action thresholds&lt;/li&gt;
&lt;li&gt;how uncertainty is handled&lt;/li&gt;
&lt;li&gt;when the agent asks instead of acts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those are not cosmetic differences.&lt;/p&gt;

&lt;p&gt;They influence what the model actually does.&lt;/p&gt;

&lt;p&gt;So if the same model scores 10/10 in one coding agent and 0/10 in another, the agent harness cannot reasonably be treated as an implementation detail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The agent harness is part of the system being evaluated.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That also means statements like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Gemini is bad at this.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;or:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Kimi follows this instruction perfectly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;are too broad for the data I am seeing.&lt;/p&gt;

&lt;p&gt;What I actually tested was a model-agent combination.&lt;/p&gt;

&lt;p&gt;A benchmark that names the model but hides the harness is leaving out part of the experiment.&lt;/p&gt;

&lt;h2&gt;
  
  
  I had already seen this before the formal matrix
&lt;/h2&gt;

&lt;p&gt;The formal numbers were not my first hint.&lt;/p&gt;

&lt;p&gt;In earlier informal runs I used the exact same case and Qwen3.8 27B through OpenRouter.&lt;/p&gt;

&lt;p&gt;With opencode and Kimi Code, the agent did the investigation correctly.&lt;/p&gt;

&lt;p&gt;It checked &lt;code&gt;context/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It inspected git history.&lt;/p&gt;

&lt;p&gt;It found no rationale explaining the strange code.&lt;/p&gt;

&lt;p&gt;And then it deleted it anyway.&lt;/p&gt;

&lt;p&gt;Only afterward did it ask.&lt;/p&gt;

&lt;p&gt;Pi, using the same model and provider, asked &lt;strong&gt;before&lt;/strong&gt; modifying the code every time I ran it.&lt;/p&gt;

&lt;p&gt;That was the moment the agent harness became interesting to me as its own variable.&lt;/p&gt;

&lt;p&gt;The current formal matrix no longer reproduces that exact Qwen split. In the documented matrix, Qwen3.8 performs well across the tested OpenRouter harnesses.&lt;/p&gt;

&lt;p&gt;That is useful information too.&lt;/p&gt;

&lt;p&gt;LLMs are probabilistic.&lt;/p&gt;

&lt;p&gt;Agents change.&lt;/p&gt;

&lt;p&gt;Harnesses change.&lt;/p&gt;

&lt;p&gt;A single run is not a statistical result.&lt;/p&gt;

&lt;p&gt;The matrix is deliberately a spot check.&lt;/p&gt;

&lt;p&gt;A failure is a lead worth investigating, not a permanent verdict.&lt;/p&gt;

&lt;p&gt;But the broader effect remains visible across multiple other models in the matrix.&lt;/p&gt;

&lt;h2&gt;
  
  
  The opposite pattern matters too
&lt;/h2&gt;

&lt;p&gt;Not every model is equally sensitive to the harness.&lt;/p&gt;

&lt;p&gt;Grok 4.6 is remarkably consistent in the matrix snapshot:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cline&lt;/td&gt;
&lt;td&gt;Pass - 10/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codex CLI&lt;/td&gt;
&lt;td&gt;Pass - 10/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kimi Code&lt;/td&gt;
&lt;td&gt;Pass - 10/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;opencode&lt;/td&gt;
&lt;td&gt;Pass - 9/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pi&lt;/td&gt;
&lt;td&gt;Pass - 9/10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Mistral Medium 3.5 shows another kind of consistency:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cline&lt;/td&gt;
&lt;td&gt;Fail - 2/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codex CLI&lt;/td&gt;
&lt;td&gt;Fail - 1/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kimi Code&lt;/td&gt;
&lt;td&gt;Fail - 2/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;opencode&lt;/td&gt;
&lt;td&gt;Fail - 1/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pi&lt;/td&gt;
&lt;td&gt;Fail - 2/10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So this is not an argument that the model does not matter.&lt;/p&gt;

&lt;p&gt;Of course the model matters.&lt;/p&gt;

&lt;p&gt;The interesting result is that &lt;strong&gt;model capability and harness behavior interact&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Some models seem robust across very different agents.&lt;/p&gt;

&lt;p&gt;Others appear much more harness-sensitive.&lt;/p&gt;

&lt;p&gt;That sensitivity may itself be something worth measuring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two eval views: depth and breadth
&lt;/h2&gt;

&lt;p&gt;The cross-agent matrix is not trying to replace a full behavioral eval suite.&lt;/p&gt;

&lt;p&gt;They answer different questions.&lt;/p&gt;

&lt;p&gt;A deep behavioral suite asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does the skill behave correctly across many different situations?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The matrix asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What changes when I hold one representative situation roughly constant and vary the model-agent combination?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One goes deep across behaviors.&lt;/p&gt;

&lt;p&gt;The other goes wide across systems.&lt;/p&gt;

&lt;p&gt;That distinction is important because running every behavioral case against every model-agent combination would quickly become expensive, slow, and difficult to interpret.&lt;/p&gt;

&lt;p&gt;So the matrix deliberately uses a representative case as a spot check.&lt;/p&gt;

&lt;p&gt;Each judged cell gets two outputs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a pass/fail verdict against the expected behavior&lt;/li&gt;
&lt;li&gt;a score from 0 to 10 describing how closely the run matched it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A &lt;code&gt;10/10&lt;/code&gt; therefore does &lt;strong&gt;not&lt;/strong&gt; mean ten independent tests passed.&lt;/p&gt;

&lt;p&gt;It is the score for that individual run.&lt;/p&gt;

&lt;p&gt;Likewise, one failed cell does not establish that the combination always fails.&lt;/p&gt;

&lt;p&gt;The same judging setup is used across the matrix so the comparison itself stays consistent.&lt;/p&gt;

&lt;p&gt;There is also an important scope decision around skill loading.&lt;/p&gt;

&lt;p&gt;The purpose of the cross-agent comparison is primarily to observe what the agent does &lt;strong&gt;once it has the skill available&lt;/strong&gt;, not to turn every cell into a separate test of each product's skill-discovery mechanism.&lt;/p&gt;

&lt;p&gt;Where necessary, drivers are therefore given the exact skill path explicitly.&lt;/p&gt;

&lt;p&gt;That keeps the main variable closer to the thing I actually want to observe:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How does this model-agent system behave after receiving the same skill and task?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://keepthewhy.com/agent-matrix/" rel="noopener noreferrer"&gt;live matrix&lt;/a&gt; documents the exact methodology, versions, dates, and per-cell details.&lt;/p&gt;

&lt;p&gt;It is the source of truth as the matrix evolves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then I discovered that my eval setup could lie too
&lt;/h2&gt;

&lt;p&gt;This is a separate lesson from the agent-harness effect.&lt;/p&gt;

&lt;p&gt;While building the matrix, the first major problems I found were not model failures.&lt;/p&gt;

&lt;p&gt;They were bugs in my own &lt;strong&gt;eval harness&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  opencode was operating on the wrong repository
&lt;/h3&gt;

&lt;p&gt;The eval runner creates an isolated fixture repository for each test.&lt;/p&gt;

&lt;p&gt;At least, that was the idea.&lt;/p&gt;

&lt;p&gt;Without an explicit &lt;code&gt;--dir&lt;/code&gt;, opencode did not use that fixture as its project root.&lt;/p&gt;

&lt;p&gt;The run looked normal.&lt;/p&gt;

&lt;p&gt;The transcript looked normal.&lt;/p&gt;

&lt;p&gt;Tools executed normally.&lt;/p&gt;

&lt;p&gt;It was simply operating on the real Keep the Why repository instead.&lt;/p&gt;

&lt;p&gt;One run even created a &lt;code&gt;context/&lt;/code&gt; entry there.&lt;/p&gt;

&lt;p&gt;I caught it before committing anything.&lt;/p&gt;

&lt;p&gt;But the important part is what would have happened otherwise.&lt;/p&gt;

&lt;p&gt;I could have obtained a perfectly plausible pass/fail result for a test that had never actually run against the intended repository.&lt;/p&gt;

&lt;p&gt;Those results were not failures.&lt;/p&gt;

&lt;p&gt;They were not passes.&lt;/p&gt;

&lt;p&gt;They were &lt;strong&gt;invalid&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Codex CLI had a different problem
&lt;/h3&gt;

&lt;p&gt;Codex CLI had a different setup issue.&lt;/p&gt;

&lt;p&gt;The non-interactive run was initially configured in a way that prevented the writes required by the test.&lt;/p&gt;

&lt;p&gt;Again, the transcript could still look plausible.&lt;/p&gt;

&lt;p&gt;That creates a nasty ambiguity.&lt;/p&gt;

&lt;p&gt;Did the agent decide not to modify the file?&lt;/p&gt;

&lt;p&gt;Or was the environment structurally preventing the modification?&lt;/p&gt;

&lt;p&gt;Those are completely different things.&lt;/p&gt;

&lt;p&gt;From a shallow look at the final repository, they can look identical.&lt;/p&gt;

&lt;p&gt;Once the driver configuration was corrected, the runs became meaningful.&lt;/p&gt;

&lt;p&gt;Again, none of this says anything interesting about model intelligence.&lt;/p&gt;

&lt;p&gt;It says something about whether the measurement itself is valid.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fail loud applies to evals too
&lt;/h2&gt;

&lt;p&gt;I have always preferred production systems that fail loudly.&lt;/p&gt;

&lt;p&gt;A crash is annoying, but obvious.&lt;/p&gt;

&lt;p&gt;You investigate it.&lt;/p&gt;

&lt;p&gt;A wrong result that looks reasonable is much more dangerous.&lt;/p&gt;

&lt;p&gt;That applies surprisingly well to AI evals.&lt;/p&gt;

&lt;p&gt;Both bugs produced something that could easily have ended up in a polished benchmark table.&lt;/p&gt;

&lt;p&gt;Nothing screamed:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;YOUR DATA IS INVALID.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The numbers would simply have been wrong.&lt;/p&gt;

&lt;p&gt;So there are actually two different harness lessons here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The agent harness changes the behavior you are measuring.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And separately:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The eval harness determines whether you are measuring it correctly.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;They should not be confused.&lt;/p&gt;

&lt;p&gt;But both deserve attention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Another failure mode surprised me even more
&lt;/h2&gt;

&lt;p&gt;Some models did not simply ignore the Chesterton's Fence behavior.&lt;/p&gt;

&lt;p&gt;They performed most of it correctly.&lt;/p&gt;

&lt;p&gt;They checked the Keep the Why context.&lt;/p&gt;

&lt;p&gt;They inspected git history.&lt;/p&gt;

&lt;p&gt;They correctly discovered that there was no confirmed rationale for the code.&lt;/p&gt;

&lt;p&gt;Then they removed it anyway.&lt;/p&gt;

&lt;p&gt;That is already interesting.&lt;/p&gt;

&lt;p&gt;But in some runs the agent subsequently wrote a Keep the Why context entry and marked the rationale as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Evidence: confirmed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing had been confirmed.&lt;/p&gt;

&lt;p&gt;The agent had created evidence of compliance after violating the rule.&lt;/p&gt;

&lt;p&gt;I find that more concerning than simply skipping an instruction.&lt;/p&gt;

&lt;p&gt;A shallow eval might see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;context entry exists&lt;/li&gt;
&lt;li&gt;expected fields exist&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Evidence&lt;/code&gt; is present&lt;/li&gt;
&lt;li&gt;file syntax is valid&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and call it success.&lt;/p&gt;

&lt;p&gt;But the semantic claim is false.&lt;/p&gt;

&lt;p&gt;The output has the &lt;strong&gt;shape of correctness&lt;/strong&gt; without the underlying truth.&lt;/p&gt;

&lt;p&gt;That is now something I specifically want evals to detect.&lt;/p&gt;

&lt;p&gt;Not just bad answers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fabricated compliance.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost adds another dimension
&lt;/h2&gt;

&lt;p&gt;Correctness was not the only thing the matrix exposed.&lt;/p&gt;

&lt;p&gt;Agent loops can make model pricing behave very differently from headline per-token prices.&lt;/p&gt;

&lt;p&gt;Context grows with every turn.&lt;/p&gt;

&lt;p&gt;Previous messages stay around.&lt;/p&gt;

&lt;p&gt;Tool output accumulates.&lt;/p&gt;

&lt;p&gt;Repository context may be repeatedly reused.&lt;/p&gt;

&lt;p&gt;Caching behavior and provider pricing can therefore matter a lot.&lt;/p&gt;

&lt;p&gt;In one matrix run, Mistral Medium 3.5 averaged around &lt;strong&gt;$3.77 per run&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The other models in that comparison landed between roughly &lt;strong&gt;$0.26 and $1.22&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Mistral did not even have the highest raw per-token price.&lt;/p&gt;

&lt;p&gt;But the actual agent loop was dramatically more expensive.&lt;/p&gt;

&lt;p&gt;That gave me another useful distinction:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which model is cheap per token?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;is not necessarily the same question as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which model is cheap to operate as an agent?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If I am evaluating a real coding setup, I care about the second one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local models turned into a compatibility test
&lt;/h2&gt;

&lt;p&gt;I also pointed the matrix at a local Qwen3.8 27B Q4_K_M instance through Ollama.&lt;/p&gt;

&lt;p&gt;Pi produced a clean 9/10 result.&lt;/p&gt;

&lt;p&gt;opencode produced 2/10.&lt;/p&gt;

&lt;p&gt;Other agent combinations were attempted but did not produce meaningful model/skill verdicts because they hit practical integration constraints in the tested setup.&lt;/p&gt;

&lt;p&gt;That is useful data too.&lt;/p&gt;

&lt;p&gt;There is a fundamental difference between:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The agent completed the task and behaved incorrectly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This combination did not reach a point where the task could be evaluated.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Turning both into the same red &lt;code&gt;FAIL&lt;/code&gt; creates a nicer-looking table.&lt;/p&gt;

&lt;p&gt;It also destroys information.&lt;/p&gt;

&lt;p&gt;The live matrix records those blockers separately instead of pretending they are reasoning failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm taking away from this
&lt;/h2&gt;

&lt;p&gt;The biggest lesson is not which row currently has the most green cells.&lt;/p&gt;

&lt;p&gt;It is that &lt;strong&gt;a model does not have one fixed "coding agent performance."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The surrounding agent matters.&lt;/p&gt;

&lt;p&gt;Sometimes enormously.&lt;/p&gt;

&lt;p&gt;If I want to know whether a setup is safe and useful for my work, I need to test the system I am actually going to run.&lt;/p&gt;

&lt;p&gt;Not just the model behind it.&lt;/p&gt;

&lt;p&gt;That means:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The model matters.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Some models are much more consistent across harnesses than others.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The agent harness matters.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The same model can produce dramatically different results depending on the coding agent around it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The interaction matters.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A strong model in one harness can become a much weaker system in another.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The eval harness matters too, but for a different reason.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the test environment is broken, a benchmark can confidently measure something that never happened.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fabricated compliance deserves explicit testing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An agent can produce artifacts that look correct while inventing the evidence behind them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost and compatibility belong in the result.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A system that is correct but unusably expensive, slow, or incompatible may still be the wrong system for the job.&lt;/p&gt;

&lt;p&gt;And most importantly:&lt;/p&gt;

&lt;h2&gt;
  
  
  Benchmark the system you actually use
&lt;/h2&gt;

&lt;p&gt;A model leaderboard is useful.&lt;/p&gt;

&lt;p&gt;But a coding agent is more than a model endpoint.&lt;/p&gt;

&lt;p&gt;It is a model embedded in instructions, context, tools, permissions, control loops, and implementation choices.&lt;/p&gt;

&lt;p&gt;Those things can change the result.&lt;/p&gt;

&lt;p&gt;So after running this matrix, I would be very cautious about saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Model X is better at agentic coding than Model Y.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;without adding:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In which agent?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The live matrix, including exact versions, dates, methodology, and per-cell results, is here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://keepthewhy.com/agent-matrix/" rel="noopener noreferrer"&gt;keepthewhy.com/agent-matrix/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It gets updated as new combinations are tested and specific findings are re-checked.&lt;/p&gt;

&lt;p&gt;The concrete scores shown in this article are a snapshot.&lt;/p&gt;

&lt;p&gt;The live matrix is the source of truth.&lt;/p&gt;

&lt;p&gt;This is one skill, one representative cross-agent case, and a growing amount of data.&lt;/p&gt;

&lt;p&gt;It is not enough to rank the world's coding models.&lt;/p&gt;

&lt;p&gt;But it is enough to make me stop treating the &lt;strong&gt;agent harness&lt;/strong&gt; as plumbing.&lt;/p&gt;




&lt;p&gt;I hope you found this informative and useful.&lt;/p&gt;

&lt;p&gt;Follow me on &lt;a href="https://github.com/oliver-zehentleitner" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, &lt;a href="https://bsky.app/profile/o-zehentleitner.bsky.social" rel="noopener noreferrer"&gt;Bluesky&lt;/a&gt;, &lt;a href="https://burningboard.net/@oliverzehentleitner" rel="noopener noreferrer"&gt;Mastodon&lt;/a&gt;, &lt;a href="https://x.com/unicorn_oz" rel="noopener noreferrer"&gt;X&lt;/a&gt;, and &lt;a href="https://www.linkedin.com/in/oliver-zehentleitner/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, or join &lt;a href="https://t.me/unicorndevs" rel="noopener noreferrer"&gt;Telegram&lt;/a&gt; for updates on my latest publications. Constructive feedback is always appreciated.&lt;/p&gt;

&lt;p&gt;Thank you for reading, and happy coding! ¯\_(ツ)_/¯&lt;/p&gt;

</description>
    </item>
    <item>
      <title>The Complete Binance Python API Guide (2026)</title>
      <dc:creator>Oliver Zehentleitner</dc:creator>
      <pubDate>Mon, 20 Jul 2026 11:53:29 +0000</pubDate>
      <link>https://dev.to/oliverzehentleitner/the-complete-binance-python-api-guide-2026-35hf</link>
      <guid>https://dev.to/oliverzehentleitner/the-complete-binance-python-api-guide-2026-35hf</guid>
      <description>&lt;h2&gt;
  
  
  REST, WebSocket, order books, trailing stops, and cluster-scale infrastructure — the production-grade Python stack for Binance.
&lt;/h2&gt;

&lt;p&gt;If you Google &lt;strong&gt;"python binance"&lt;/strong&gt; in 2026, the first hits are &lt;code&gt;python-binance&lt;/code&gt;, &lt;code&gt;binance-connector-python&lt;/code&gt;, and &lt;code&gt;CCXT&lt;/code&gt;. Useful tools, all of them. But once a Binance bot moves from &lt;em&gt;script&lt;/em&gt; to &lt;em&gt;service&lt;/em&gt;, the interesting question changes: not only &lt;em&gt;can this library call the endpoint?&lt;/em&gt;, but &lt;em&gt;does it give me the operational model to keep REST, WebSocket streams, WebSocket API trading requests, order state, reconnects, depth caches, and failure handling under control?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;One library with a more operational focus still barely shows up in search results: the &lt;a href="https://blog.technopathy.club/page/unicorn-binance-suite" rel="noopener noreferrer"&gt;&lt;strong&gt;UNICORN Binance Suite&lt;/strong&gt;&lt;/a&gt; (UBS). 3.3M+ downloads. 390+ public dependent projects. Several interlocking packages, all MIT, all maintained by one developer with a public name, public GitHub repos, and a Telegram you can actually message.&lt;/p&gt;

&lt;p&gt;This guide is the cornerstone reference: what each tool does, why it matters, when to use which, and how the pieces fit together. With &lt;strong&gt;verified, live code&lt;/strong&gt; — every output you see in this article was captured from a real call to &lt;code&gt;api.binance.com&lt;/code&gt; while writing it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you're already sold and just want to read code, skip to Your First Binance REST Call in Python.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Python-on-Binance Landscape in 2026
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;th&gt;Honest strength&lt;/th&gt;
&lt;th&gt;Trade-off&lt;/th&gt;
&lt;th&gt;Best fit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;python-binance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Widely known Python package with broad REST coverage, WebSocket managers, many examples, tutorials, and existing bots&lt;/td&gt;
&lt;td&gt;Familiar does not automatically mean operationally simpler: stream lifecycle, trust state, and order-book correctness are less explicit than in UBS, so production behavior often has to be handled in application code&lt;/td&gt;
&lt;td&gt;Existing projects, users already invested in that ecosystem, quick experiments where operational state is not the main concern&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;binance-connector-python / official SDKs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Official Binance direction, close to the published API surface, strong REST endpoint coverage, lightweight clients, modern authentication support&lt;/td&gt;
&lt;td&gt;Intentionally low-level: good building blocks, but reconnect policy, queues, depth-cache trust, strategy state, and operational glue are mostly your responsibility&lt;/td&gt;
&lt;td&gt;Teams that want the official API surface and prefer to build their own runtime around it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CCXT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Excellent multi-exchange abstraction across multiple languages and many exchanges; great when one codebase must talk to more than Binance&lt;/td&gt;
&lt;td&gt;The abstraction is the value, but it naturally trades away some Binance-specific controls, lifecycle detail, and exchange-specific ergonomics&lt;/td&gt;
&lt;td&gt;Multi-exchange systems, portfolio tooling, arbitrage research, backtesting, and teams that do not want to be Binance-only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;UNICORN Binance Suite&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Binance-native operational stack: simple REST calls, WebSocket streams, stream lifecycle signals, WebSocket API requests, reconnects, sequence validation, out-of-sync handling, multi-account routing, asyncio queues, and cluster-scale DepthCache infrastructure&lt;/td&gt;
&lt;td&gt;Binance-only by design; explicit manager model instead of loose helper functions; strongest when REST, WebSockets, order books, and failure states need to work together&lt;/td&gt;
&lt;td&gt;Beginners who want sane defaults, Binance-specific bots, 24/7 services, local order books, and systems where failure states must be visible&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;This guide is about the fourth row.&lt;/strong&gt; Not because the others are bad. They are strong in different ways: &lt;code&gt;python-binance&lt;/code&gt; is widely known and has many examples, the official Binance SDKs closely track Binance's published API surface, and &lt;code&gt;CCXT&lt;/code&gt; is excellent when exchange abstraction matters more than Binance-specific depth. UBS is built for a narrower problem: Binance-specific systems where WebSocket lifecycle, order-book correctness, reconnect behavior, request routing, and failure states should be explicit instead of hidden in application glue.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A friendly maintainer's note: I am the author of UBS. I'll cite community sources where I can, show you working code, and let you decide. If you spot anything that looks unfair, the comments and &lt;a href="https://t.me/unicorndevs" rel="noopener noreferrer"&gt;Telegram&lt;/a&gt; are open.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What UBS Actually Is
&lt;/h2&gt;

&lt;p&gt;UBS is &lt;strong&gt;not a single library&lt;/strong&gt; — it's a coordinated suite of six packages plus an optional Kubernetes-scale service. Each piece is its own PyPI package, its own GitHub repo, its own release cadence — but the interfaces line up so they compose without glue code.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;unicorn-binance-rest-api&lt;/strong&gt; (UBRA)&lt;/td&gt;
&lt;td&gt;REST client for public and private Binance endpoints&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;unicorn-binance-websocket-api&lt;/strong&gt; (UBWA)&lt;/td&gt;
&lt;td&gt;WebSocket streams, WebSocket API requests, user-data streams, reconnects, lifecycle signals&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;unicorn-binance-local-depth-cache&lt;/strong&gt; (UBLDC)&lt;/td&gt;
&lt;td&gt;Local synchronized order books with sequence validation, pruning, resync, and &lt;code&gt;DepthCacheOutOfSync&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;unicorn-binance-trailing-stop-loss&lt;/strong&gt; (UBTSL)&lt;/td&gt;
&lt;td&gt;Trailing stop engine and CLI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;unicorn-fy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Raw Binance payloads → normalized Python dictionaries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;ubdcc&lt;/strong&gt; (UBDCC)&lt;/td&gt;
&lt;td&gt;Shared DepthCache service for local or Kubernetes-scale infrastructure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;unicorn-binance-suite&lt;/strong&gt; (meta)&lt;/td&gt;
&lt;td&gt;Installs the suite components together&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb9svhs6ndu90rlwa7mg1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb9svhs6ndu90rlwa7mg1.png" alt="UBS Products Overview" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Install in One Line
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;unicorn-binance-suite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives you the core UNICORN Binance Suite packages — UBRA, UBWA, UBLDC, UBTSL, and UnicornFy — in one install. UBDCC is different: it is the cluster service built on top of UBLDC, not a client library inside the suite. Want a single piece? Install just that one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;unicorn-binance-websocket-api
pip &lt;span class="nb"&gt;install &lt;/span&gt;unicorn-binance-rest-api
pip &lt;span class="nb"&gt;install &lt;/span&gt;unicorn-binance-local-depth-cache
pip &lt;span class="nb"&gt;install &lt;/span&gt;unicorn-binance-trailing-stop-loss
pip &lt;span class="nb"&gt;install &lt;/span&gt;unicorn-fy
pip &lt;span class="nb"&gt;install &lt;/span&gt;ubdcc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A note on a myth that still floats around: &lt;em&gt;"UBS is hard to install because it needs C build tools."&lt;/em&gt; Not since multi-arch wheels (x86_64, aarch64, arm64) were added. Where UBS uses native/Cython components, &lt;code&gt;pip install&lt;/code&gt; resolves to a pre-built binary on common platforms. Cython where it pays off, plain Python where it doesn't.&lt;/p&gt;

&lt;p&gt;You &lt;strong&gt;do not need API keys&lt;/strong&gt; for anything in the &lt;em&gt;Market Data&lt;/em&gt; sections below (REST tickers, WebSocket public streams, depth caches). For account operations and trailing stops, see &lt;a href="https://blog.technopathy.club/how-to-create-a-binance-api-key-and-api-secret" rel="noopener noreferrer"&gt;How to create a Binance API Key and API Secret&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Your First Binance REST Call in Python
&lt;/h2&gt;

&lt;p&gt;The most common starting question: &lt;strong&gt;"How do I get the current price of BTC in Python?"&lt;/strong&gt; With UBRA:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;unicorn_binance_rest_api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BinanceRestApiManager&lt;/span&gt;

&lt;span class="n"&gt;ubra&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BinanceRestApiManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;binance.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ticker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ubra&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_symbol_ticker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BTCUSDC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ubra&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop_manager&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Live output&lt;/strong&gt; (captured from &lt;code&gt;api.binance.com&lt;/code&gt; while writing this):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;symbol&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;BTCUSDC&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;81250.60000000&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;24h statistics in one call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ubra&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_ticker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BTCUSDC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lastPrice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;priceChangePercent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;highPrice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lowPrice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;volume&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;quoteVolume&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  lastPrice: 81250.60000000
  priceChangePercent: 0.394
  highPrice: 82137.26000000
  lowPrice: 80462.97000000
  volume: 11772.94006000
  quoteVolume: 957215665.33209680
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Historical candles for backtesting or charts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;klines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ubra&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_klines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BTCUSDC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1h&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;klines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  open=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  high=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  low=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  close=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  volume=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  open=81249.93000000  high=81294.42000000  low=81024.41000000  close=81058.80000000  volume=187.87319000
  open=81058.79000000  high=81303.99000000  low=81000.00000000  close=81240.01000000  volume=398.28206000
  open=81240.01000000  high=81283.80000000  low=81203.81000000  close=81250.60000000  volume=157.79464000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Authentication for account/trading endpoints&lt;/strong&gt; is one extra constructor argument:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;ubra&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BinanceRestApiManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_secret&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_API_SECRET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;binance.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ubra&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_account&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# private endpoint, requires signed request
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;UBRA supports &lt;strong&gt;com, com-margin, com-isolated-margin, com-futures, us, and tr&lt;/strong&gt;, plus all matching testnets — switch with the &lt;code&gt;exchange&lt;/code&gt; argument. A signed-order example for an OCO take-profit/stop-loss pattern lives in &lt;a href="https://blog.technopathy.club/buy-an-asset-and-instantly-create-a-take-profit-and-stop-loss-oco-sell-order-using-python-in-binance-isolated-margin" rel="noopener noreferrer"&gt;Buy an Asset and Instantly Create a Take-Profit + Stop-Loss OCO Sell Order&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Your First WebSocket Stream in Python
&lt;/h2&gt;

&lt;p&gt;REST polling is fine for one-off queries. For real-time price feeds you want WebSockets. UBWA's model is simple: &lt;strong&gt;one Manager, many streams&lt;/strong&gt;. The receiving side can be as small as a few lines, or as explicit as a dedicated asyncio queue per stream.&lt;/p&gt;

&lt;p&gt;The examples below intentionally follow the UBWA README style. Start simple, then choose the processing model that fits your bot.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1 — Pull from the stream buffer
&lt;/h3&gt;

&lt;p&gt;The shortest possible pattern. No callbacks, no asyncio in your code. UBWA receives frames in the background; you pull the oldest item from the stream buffer when you are ready.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;unicorn_binance_websocket_api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BinanceWebSocketApiManager&lt;/span&gt;

&lt;span class="n"&gt;ubwa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BinanceWebSocketApiManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;binance.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ubwa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trade&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kline_1m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;markets&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;btcusdc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bnbbtc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ethbtc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;oldest_data_from_stream_buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ubwa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop_stream_data_from_stream_buffer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;oldest_data_from_stream_buffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oldest_data_from_stream_buffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the easiest way to understand the flow: Binance pushes data asynchronously, UBWA stores it, and your code consumes it.&lt;/p&gt;

&lt;p&gt;For normalized Python dictionaries instead of raw Binance payloads, request UnicornFy output on the stream:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;ubwa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trade&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;markets&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;btcusdc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UnicornFy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When to use it: scripts, demos, notebooks, quick collectors. For long-running services, avoid a tight empty polling loop. Use a callback or an asyncio queue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2 — Callback per received frame
&lt;/h3&gt;

&lt;p&gt;You hand UBWA a normal function. Every received frame is passed to it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;unicorn_binance_websocket_api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BinanceWebSocketApiManager&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_new_receives&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream_data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream_data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="n"&gt;ubwa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BinanceWebSocketApiManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;binance.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ubwa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trade&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kline_1m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;markets&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;btcusdc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bnbbtc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ethbtc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;process_stream_data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;process_new_receives&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is also an async callback variant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;unicorn_binance_websocket_api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BinanceWebSocketApiManager&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_new_receives&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream_data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;ubwa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BinanceWebSocketApiManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;binance.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ubwa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trade&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kline_1m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;markets&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;btcusdc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bnbbtc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ethbtc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;process_stream_data_async&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;process_new_receives&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When to use it: clean event-style processing where each message can be handled independently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 3 — Await the stream data in an asyncio coroutine
&lt;/h3&gt;

&lt;p&gt;This is the README-recommended pattern for processing stream data when you want explicit async handling. UBWA creates the stream and feeds an asyncio queue; your coroutine awaits data from that queue.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;unicorn_binance_websocket_api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BinanceWebSocketApiManager&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_asyncio_queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Start processing data from stream &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ubwa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_stream_label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;ubwa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_stop_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;stream_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ubwa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_stream_data_from_asyncio_queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;stream_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;ubwa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asyncio_queue_task_done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;stream_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;ubwa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trade&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;markets&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ethbtc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;btcusdc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;stream_label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TRADES&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;process_asyncio_queue&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;process_asyncio_queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;ubwa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_manager_stopping&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;BinanceWebSocketApiManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;binance.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ubwa&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;KeyboardInterrupt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Gracefully stopping ...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What this gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;one explicit coroutine for the stream,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;a real &lt;code&gt;await&lt;/code&gt; on incoming data,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;clean backpressure via &lt;code&gt;asyncio_queue_task_done()&lt;/code&gt;,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;a manager lifecycle that exits cleanly when used with &lt;code&gt;with&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Subscribe and unsubscribe without rebuilding the stream
&lt;/h3&gt;

&lt;p&gt;UBWA can add or remove markets and channels at runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;markets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;engbtc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;zileth&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;channels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kline_5m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kline_15m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;depth5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;ubwa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe_to_stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;stream_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;markets&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;markets&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ubwa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unsubscribe_from_stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;stream_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;markets&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;markets&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ubwa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unsubscribe_from_stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;stream_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That matters in real bots. You do not want to tear down and recreate a socket every time your market universe changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's true for all receiving patterns
&lt;/h3&gt;

&lt;p&gt;Regardless of which option you pick:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;WebSocket receiving is asynchronous by nature.&lt;/strong&gt; You subscribe once; Binance pushes frames when they exist. Your job is to route and process them correctly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;UBWA owns the socket lifecycle.&lt;/strong&gt; Reconnects, listenKey renewal for user-data streams, ping/pong, and stream health happen below your strategy code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;UnicornFy is optional but useful.&lt;/strong&gt; Use &lt;code&gt;output="UnicornFy"&lt;/code&gt; when you want readable dict keys instead of raw Binance event fields.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Receiving data is not the same as knowing the stream is healthy.&lt;/strong&gt; That is what &lt;code&gt;stream_signals&lt;/code&gt; are for.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F36u4kcganf6dspw75e3w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F36u4kcganf6dspw75e3w.png" alt="Logs" width="800" height="385"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Stream Signals: Know When Your Bot Is Blind
&lt;/h2&gt;

&lt;p&gt;Receiving data is only half of a WebSocket client. The other half is knowing whether the stream itself is currently trustworthy.&lt;/p&gt;

&lt;p&gt;A WebSocket is asynchronous by nature. Data arrives when Binance pushes it. Silence can mean "no trade happened", but it can also mean "your connection is gone", "the stream is reconnecting", "the first data frame has not arrived yet", or "this stream cannot be restored". For a trading bot, those states are not cosmetic. They decide whether indicators are still valid, whether a strategy should pause, whether missing data must be reloaded via REST, or whether open positions should be handled defensively.&lt;/p&gt;

&lt;p&gt;UBWA exposes this through &lt;code&gt;stream_signals&lt;/code&gt;. They tell your code about lifecycle changes in real time:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CONNECT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The stream connection was established.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;FIRST_RECEIVED_DATA&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The first data record arrived. This is the point where the stream is no longer just connected, but actually feeding data.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DISCONNECT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The stream disconnected. UBWA includes the last received data record if available, so your code has a recovery anchor.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;STOP&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The stream was stopped.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;STREAM_UNREPAIRABLE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;UBWA cannot restore the stream, for example because of invalid credentials or an exception in your own processing coroutine.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That last distinction matters: &lt;strong&gt;connected is not the same as usable&lt;/strong&gt;. A bot that subscribes to &lt;code&gt;btcusdc@depth&lt;/code&gt; and immediately starts trading before the first data frame arrived is guessing. A bot that keeps calculating indicators after a disconnect is blind. &lt;code&gt;stream_signals&lt;/code&gt; make those states explicit.&lt;/p&gt;

&lt;p&gt;The callback version is usually the cleanest production pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;unicorn_binance_websocket_api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BinanceWebSocketApiManager&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_stream_signals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signal_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stream_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data_record&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error_msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Received stream_signal for stream &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ubwa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_stream_label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;stream_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;signal_type&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; - &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;stream_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; - &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;data_record&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; - &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;error_msg&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;BinanceWebSocketApiManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;process_stream_signals&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;process_stream_signals&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ubwa&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;ubwa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trade&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;markets&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;btcusdc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stream_label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TRADES&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For simpler scripts you can also enable the signal buffer and poll it, similar to normal stream data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;ubwa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BinanceWebSocketApiManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;binance.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;enable_stream_signal_buffer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ubwa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trade&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;markets&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;btcusdc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stream_label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BTCUSDC_TRADES&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;signal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ubwa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop_stream_signal_from_stream_signal_buffer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a small feature with huge operational impact. It turns WebSocket reliability from log-reading into code-level state. Your own application can know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;"I am connected, but not live yet."&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;"I received the first usable market-data frame."&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;"I just lost the stream and must stop trusting derived indicators."&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;"This stream is unrecoverable and needs human or strategy-level intervention."&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is also why UBWA fits so well underneath UBLDC and UBDCC. A depth cache does not only need bids and asks. It needs lifecycle truth. &lt;code&gt;CONNECT&lt;/code&gt;, &lt;code&gt;FIRST_RECEIVED_DATA&lt;/code&gt;, &lt;code&gt;DISCONNECT&lt;/code&gt;, and &lt;code&gt;STREAM_UNREPAIRABLE&lt;/code&gt; are the vocabulary that lets the next layer decide whether data is authoritative, stale, recovering, or unusable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3f9g2f9mj85gkotl4vkw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3f9g2f9mj85gkotl4vkw.png" alt="Stream Signals" width="799" height="221"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Trading Over WebSocket: Create and Cancel Orders Without REST
&lt;/h2&gt;

&lt;p&gt;There is one Binance feature many Python guides still treat as an afterthought: the &lt;strong&gt;Binance WebSocket API&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is not the same thing as a market-data WebSocket stream.&lt;/p&gt;

&lt;p&gt;A market-data stream pushes events to you: trades, klines, depth updates, user-data events. The &lt;strong&gt;WebSocket API&lt;/strong&gt; is a request/response API over a persistent WebSocket connection: place an order, cancel an order, query account state, and receive a correlated response later — without opening a new HTTP request for every action.&lt;/p&gt;

&lt;p&gt;That last word matters: &lt;strong&gt;later&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;WebSocket is asynchronous by nature. You send a request into an already-open connection and move on. At some later point Binance pushes a response frame back. Your code should not pretend that this is just REST with a different transport. The clean design is not "call function, block forever, hope the socket behaves." The clean design is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;send a request with an ID,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;receive frames asynchronously,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;route the matching response to the right handler,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;keep the connection lifecycle independent from the strategy logic.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For slow scripts, REST is fine. For systems that already live on WebSockets, jumping back to REST for trading actions creates an awkward split:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;market data arrives over WebSocket,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the strategy reacts in memory,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;order placement jumps back to REST,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;response handling follows a different path,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;request IDs, retries, state reconciliation, and failure handling become your problem.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;UBWA supports Binance WebSocket API requests directly through the same manager model used for streams.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;unicorn_binance_websocket_api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BinanceWebSocketApiManager&lt;/span&gt;

&lt;span class="n"&gt;ubwa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BinanceWebSocketApiManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;binance.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_secret&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_API_SECRET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;output_default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dict&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;api_stream_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ubwa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ubwa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;spot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;stream_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;api_stream_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BTCUSDC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;side&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BUY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;order_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LIMIT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;time_in_force&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GTC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;quantity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.001&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;50000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# The response is pushed back by Binance and handled asynchronously.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the natural WebSocket way: send the request, keep the socket alive, process the response when it arrives.&lt;/p&gt;

&lt;p&gt;For scripts, demos, tests, or migration from REST-style code, UBWA can also wait for the matching response and return it directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ubwa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;spot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;stream_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;api_stream_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BTCUSDC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;side&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BUY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;order_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LIMIT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;time_in_force&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GTC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;quantity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.001&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;50000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;return_response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Canceling an order follows the same model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;ubwa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;spot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cancel_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;stream_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;api_stream_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BTCUSDC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;123456789&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is not only that order placement and cancellation work over WebSocket. The important part is &lt;strong&gt;how responses can be processed&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;UBWA lets you handle WebSocket API responses in several ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;global callback,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;stream-specific callback,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;request-specific callback,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;async handler,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;blocking &lt;code&gt;return_response=True&lt;/code&gt;,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;or the normal stream buffer.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That makes the same feature usable in a notebook, a CLI tool, a long-running bot, or a service with dedicated request routing.&lt;/p&gt;

&lt;p&gt;A small but useful detail: &lt;code&gt;stream_id=api_stream_id&lt;/code&gt; is only required when more than one WebSocket API stream is active. If there is exactly one WebSocket API stream, UBWA can use that stream automatically. In simple examples I still pass the &lt;code&gt;stream_id&lt;/code&gt; explicitly because it makes the routing visible.&lt;/p&gt;

&lt;p&gt;The real advantage shows up in multi-account or multi-key setups: you can run several WebSocket API streams with different &lt;code&gt;api_key&lt;/code&gt; / &lt;code&gt;api_secret&lt;/code&gt; pairs and then explicitly route a request to the stream that belongs to the right account.&lt;/p&gt;

&lt;p&gt;To stay fair: this is no longer a UBS-only checkbox. &lt;code&gt;python-binance&lt;/code&gt; documents &lt;a href="https://python-binance.readthedocs.io/en/latest/websockets.html" rel="noopener noreferrer"&gt;API requests via WebSockets&lt;/a&gt;, Binance's official &lt;code&gt;binance-sdk-spot&lt;/code&gt; describes itself as supporting REST API, WebSocket API, and WebSocket Streams, and CCXT/CCXT Pro has exchange-dependent WebSocket support. The difference is the operational model: in UBWA, WebSocket streams, WebSocket API requests, reconnect handling, callbacks, stream buffers, async queues, and request routing all live inside one Binance-native manager.&lt;/p&gt;

&lt;p&gt;A full walkthrough lives here: &lt;a href="https://blog.technopathy.club/create-and-cancel-orders-via-websocket-on-binance" rel="noopener noreferrer"&gt;Create and Cancel Orders via WebSocket on Binance&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is where UBS becomes more than "REST client plus WebSocket client". REST, market streams, user-data streams, WebSocket API trading requests, response routing, and reconnect handling fit into one mental model.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Local Order Book Without the Pain (UBLDC)
&lt;/h2&gt;

&lt;p&gt;If your strategy reads the order book more than a few times per second, REST polling is a dead end: every call is a round trip across the internet, you will hit rate limits, and the data is stale by the time you parse it. The right answer is a &lt;strong&gt;local depth cache&lt;/strong&gt; — Binance pushes diffs over WebSocket, you keep a synchronized copy in memory.&lt;/p&gt;

&lt;p&gt;The naive way to do this is on the third page of Binance's docs. The right way is what UBLDC does: create the cache, read asks and bids, and let the manager handle synchronization, pruning, and resync logic.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;unicorn_binance_local_depth_cache&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BinanceLocalDepthCacheManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DepthCacheOutOfSync&lt;/span&gt;

&lt;span class="n"&gt;ubldc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BinanceLocalDepthCacheManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;binance.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;depth_cache_update_interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ubldc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_depthcache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BTCUSDC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Wait for the initial REST snapshot + WebSocket diff synchronization.
&lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;ubldc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_depth_cache_synchronized&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BTCUSDC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;asks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ubldc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_asks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;market&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BTCUSDC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit_count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;bids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ubldc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_bids&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;market&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BTCUSDC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit_count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Top 5 asks:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qty&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;asks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  qty=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;qty&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Top 5 bids:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qty&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;bids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  qty=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;qty&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;DepthCacheOutOfSync&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BTCUSDC depth cache is currently out of sync — skip this decision cycle.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ubldc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop_manager&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That exception is the important contract.&lt;/p&gt;

&lt;p&gt;A local order book can temporarily be unusable: initial snapshot still loading, sequence gap detected, reconnect in progress, resync running. UBLDC does &lt;strong&gt;not&lt;/strong&gt; silently pretend that stale memory is still authoritative. If you access the book while it is out of sync, it can raise &lt;code&gt;DepthCacheOutOfSync&lt;/code&gt;. Your strategy can then pause, skip this tick, reduce risk, or wait for the cache to recover.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reading the book the way strategies actually need it
&lt;/h3&gt;

&lt;p&gt;Most strategies don't want "the full book." They want &lt;strong&gt;the first N levels&lt;/strong&gt; or &lt;strong&gt;enough depth to fill X quote-units of volume&lt;/strong&gt;. UBLDC's &lt;code&gt;get_asks&lt;/code&gt; / &lt;code&gt;get_bids&lt;/code&gt; accept both forms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# First 10 levels on each side
&lt;/span&gt;&lt;span class="n"&gt;asks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ubldc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_asks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BTCUSDC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit_count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;bids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ubldc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_bids&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BTCUSDC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit_count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# All levels until cumulative volume crosses 300 000, e.g. USDT for a USDT pair
&lt;/span&gt;&lt;span class="n"&gt;asks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ubldc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_asks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BTCUSDC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;threshold_volume&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;bids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ubldc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_bids&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BTCUSDC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;threshold_volume&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;limit_count=N&lt;/code&gt; trims the result to the top N levels by price. &lt;code&gt;threshold_volume=X&lt;/code&gt; walks the book outward until the cumulative quote volume crosses X and stops there — exactly what you need to estimate "how far would I move the price if I market-bought X USDT of BTC right now?" without slicing the full book yourself.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;DepthCacheOutOfSync&lt;/code&gt; is the production boundary
&lt;/h3&gt;

&lt;p&gt;You do &lt;strong&gt;not&lt;/strong&gt; have to call &lt;code&gt;is_depth_cache_synchronized()&lt;/code&gt; before every read. That is useful for dashboards, readiness checks, or explicit control flow. The stronger production pattern is to treat reads as authoritative only if they succeed, and to handle &lt;code&gt;DepthCacheOutOfSync&lt;/code&gt; where your strategy consumes the book:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;unicorn_binance_local_depth_cache&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DepthCacheOutOfSync&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;asks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ubldc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_asks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BTCUSDC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit_count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;bids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ubldc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_bids&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BTCUSDC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit_count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;DepthCacheOutOfSync&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BTCUSDC depth cache out of sync — skipping decision cycle&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;

&lt;span class="c1"&gt;# If execution reaches this point, the strategy has a usable book snapshot.
&lt;/span&gt;&lt;span class="nf"&gt;run_strategy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;asks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bids&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That distinction matters. A boolean pre-check can be stale by the time you act on it. The exception is raised at the access boundary, exactly where trust is needed.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;is_depth_cache_synchronized("BTCUSDC")&lt;/code&gt; still has a place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;ubldc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_depth_cache_synchronized&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BTCUSDC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BTCUSDC still initializing or resyncing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use it for observability and readiness. Use &lt;code&gt;DepthCacheOutOfSync&lt;/code&gt; for correctness.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sidebar — Binance's docs are incomplete about depth caches
&lt;/h3&gt;

&lt;p&gt;This is the part most libraries get subtly wrong, and most users never notice until their P&amp;amp;L starts drifting.&lt;/p&gt;

&lt;p&gt;Binance's published depth-cache algorithm is &lt;strong&gt;incomplete&lt;/strong&gt; in one specific way: when a price level falls out of the top-1000, Binance stops sending updates for it — but &lt;strong&gt;never sends a "delete" event&lt;/strong&gt; for it either. A library that follows the documentation blindly will accumulate orphaned levels forever. Your "order book" turns into a museum of orders that no longer exist. Strategies that key off the depth profile slowly start trading against ghosts.&lt;/p&gt;

&lt;p&gt;UBLDC actively prunes out-of-scope levels. I ran a 25-hour side-by-side experiment to quantify the rot: a naive cache built strictly to spec versus the UBLDC-style pruned cache, fed by the same WebSocket stream. After 9 hours the naive cache was at &lt;strong&gt;~34% bid-match / ~45% ask-match&lt;/strong&gt; against a fresh REST snapshot, with 22 000 stale levels still in memory. The pruned cache held a steady ~1050 levels at 90–97% match for the full run.&lt;/p&gt;

&lt;p&gt;Full write-up with charts and raw data: &lt;a href="https://blog.technopathy.club/your-binance-depthcache-is-rotting-here-s-the-proof-in-25-hours" rel="noopener noreferrer"&gt;&lt;strong&gt;Your Binance DepthCache Is Rotting — Here's the Proof in 25 Hours&lt;/strong&gt;&lt;/a&gt;. If you build your own depth cache or use a library that doesn't handle this, please at least read the comparison chart.&lt;/p&gt;

&lt;p&gt;UBLDC additionally:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;validates &lt;code&gt;U&lt;/code&gt; / &lt;code&gt;u&lt;/code&gt; sequence numbers on every update and resynchronizes on gap detection,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;raises &lt;code&gt;DepthCacheOutOfSync&lt;/code&gt; instead of silently serving stale data while a cache is unusable,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;buffers WebSocket events during the initial REST snapshot load,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;removes orphaned out-of-scope levels beyond the top-1000 corridor,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;runs many caches in one Manager.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://oliver-zehentleitner.github.io/binance-depthcache-forensics/comparison.html" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fli4nr6fkoj8eftsz3gtb.png" alt="Chart" width="799" height="423"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Trailing Stop Loss from the CLI or Python (UBTSL)
&lt;/h2&gt;

&lt;p&gt;Risk management is the second-most-asked Binance-Python topic after "how do I get the price?"&lt;/p&gt;

&lt;p&gt;A trailing stop looks simple from the outside: follow the market while it moves in your favor, keep moving the stop behind it, and exit when the market reverses far enough. In practice, the annoying parts are all operational:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;where to store API keys,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;how to test connectivity before risking a live order,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;how to run the trailing engine from a terminal,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;how to reuse predefined profiles,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;and how to integrate the same logic into Python code when the CLI is not enough.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is what &lt;strong&gt;UNICORN Binance Trailing Stop Loss&lt;/strong&gt; (UBTSL) is for.&lt;/p&gt;

&lt;h3&gt;
  
  
  CLI workflow
&lt;/h3&gt;

&lt;p&gt;The CLI is the fastest way to use UBTSL directly from a terminal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# add Binance API key and secret&lt;/span&gt;
ubtsl &lt;span class="nt"&gt;--createconfigini&lt;/span&gt;
ubtsl &lt;span class="nt"&gt;--openconfigini&lt;/span&gt;

&lt;span class="c"&gt;# test connectivity&lt;/span&gt;
ubtsl &lt;span class="nt"&gt;--test&lt;/span&gt; binance-connectivity

&lt;span class="c"&gt;# start trailing&lt;/span&gt;
ubtsl &lt;span class="nt"&gt;-m&lt;/span&gt; BTCUSDC &lt;span class="nt"&gt;-n&lt;/span&gt; trail &lt;span class="nt"&gt;--stoplosslimit&lt;/span&gt; 1% &lt;span class="nt"&gt;-e&lt;/span&gt; binance.com

&lt;span class="c"&gt;# use a profile&lt;/span&gt;
ubtsl &lt;span class="nt"&gt;--createprofilesini&lt;/span&gt;
ubtsl &lt;span class="nt"&gt;--openprofilesini&lt;/span&gt;
ubtsl &lt;span class="nt"&gt;--profile&lt;/span&gt; BTCUSDC_SELL &lt;span class="nt"&gt;--stoplosslimit&lt;/span&gt; 1.5%

&lt;span class="c"&gt;# cancel all open orders on the configured account/exchange&lt;/span&gt;
ubtsl &lt;span class="nt"&gt;--profile&lt;/span&gt; BTCUSDC_SELL &lt;span class="nt"&gt;--cancelopenorders&lt;/span&gt;

&lt;span class="c"&gt;# help&lt;/span&gt;
ubtsl &lt;span class="nt"&gt;-h&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The normal flow is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;create the config file,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;add your Binance API key and secret,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;test connectivity,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;start a trailing stop directly or through a named profile.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Profiles are useful once you repeatedly trade the same market or strategy pattern. Instead of passing every detail on the command line each time, you define the profile once and then override only the values you want to change, such as &lt;code&gt;--stoplosslimit&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The example above starts a trailing stop on &lt;code&gt;BTCUSDC&lt;/code&gt; with a 1% stop-loss limit on &lt;code&gt;binance.com&lt;/code&gt;. The profile example starts the predefined &lt;code&gt;BTCUSDC_SELL&lt;/code&gt; setup and overrides the stop-loss limit to 1.5%.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--cancelopenorders&lt;/code&gt; is a practical cleanup command when you intentionally want to cancel all currently open orders on the configured account/exchange before starting fresh. Use it deliberately — it does exactly what the name says.&lt;/p&gt;

&lt;h3&gt;
  
  
  Python integration
&lt;/h3&gt;

&lt;p&gt;UBTSL can also be used from Python when the trailing stop should be part of a larger bot or service. The exact parameters depend on the trade direction, market, profile, and execution mode you want to use, so the official example should be treated as the reference implementation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/oliver-zehentleitner/unicorn-binance-trailing-stop-loss/blob/master/example_binance_trailing_stop_loss.py" rel="noopener noreferrer"&gt;example_binance_trailing_stop_loss.py&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The important architectural point is this: UBTSL is not just a small formula that calculates a moving stop price. It is an engine around Binance execution state. It tracks the market, updates the stop logic, reacts to partial fills and finished orders, and gives you callbacks for operational handling.&lt;/p&gt;

&lt;p&gt;A minimal SDK integration usually follows this shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;unicorn_binance_trailing_stop_loss.manager&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BinanceTrailingStopLossManager&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;callback_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error_msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ERROR: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;error_msg&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;callback_finished&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FINISHED: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;callback_partially_filled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PARTIALLY FILLED: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;BinanceTrailingStopLossManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;api_secret&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_API_SECRET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;market&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BTCUSDC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;stop_loss_limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.5%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;callback_error&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;callback_error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;callback_finished&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;callback_finished&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;callback_partially_filled&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;callback_partially_filled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ubtsl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;ubtsl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_manager_stopping&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;KeyboardInterrupt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Gracefully stopping ...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a production bot, I would not paste this blindly and call it done. I would start from the official example, wire the callbacks into your own logging/alerting, and make sure the account, market, order side, and stop-loss behavior match your intended execution model.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frn247mvw9005fjv9irar.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frn247mvw9005fjv9irar.jpg" alt="Trailing Stop Loss" width="799" height="447"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Stop Parsing Raw Binance JSON by Hand (UnicornFy)
&lt;/h2&gt;

&lt;p&gt;Binance's raw WebSocket frames are compact and cryptic. Here's a kline update straight off the wire:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"stream"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"btcusdc@kline_1m"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"e"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"kline"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"E"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1778563770000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"s"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"BTCUSDC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"k"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"t"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1778563740000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"T"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1778563799999&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"s"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"BTCUSDC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"i"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"1m"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"o"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"81251.35"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"c"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"81292.61"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"h"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"81293.00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"l"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"81250.61"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"v"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"1.42442"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"n"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"x"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"q"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"115789.12"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"V"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"0.81"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"Q"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"65872.55"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"B"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;}}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Single-letter keys. Nested envelopes. Easy to mis-parse, hard to read at a glance. UnicornFy turns that into:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;unicorn_fy.unicorn_fy&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;UnicornFy&lt;/span&gt;
&lt;span class="n"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UnicornFy&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;binance_com_websocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stream_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;btcusdc@kline_1m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;event_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kline&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;event_time&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1778563770000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;symbol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BTCUSDC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kline&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kline_start_time&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1778563740000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kline_close_time&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1778563799999&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;symbol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BTCUSDC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;interval&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;open_price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;81251.35&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;close_price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;81292.61&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;high_price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;81293.00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;low_price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;81250.61&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;base_volume&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.42442&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;number_of_trades&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;is_closed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;quote&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;115789.12&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;taker_by_base_asset_volume&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.81&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;taker_by_quote_asset_volume&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;65872.55&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You usually don't call UnicornFy directly — pass &lt;code&gt;output_default="UnicornFy"&lt;/code&gt; to UBWA and every frame that lands in your buffer is already normalized.&lt;/p&gt;




&lt;h2&gt;
  
  
  Scaling Beyond a Single Process: UBDCC
&lt;/h2&gt;

&lt;p&gt;A single UBLDC process can handle many markets. The moment you need &lt;strong&gt;redundancy&lt;/strong&gt; or &lt;strong&gt;multiple consumers&lt;/strong&gt;, the order book should stop living inside one bot process.&lt;/p&gt;

&lt;p&gt;UBDCC turns UBLDC into a shared service. You run the cluster once, create DepthCaches there, and every bot, dashboard, or service reads the same synchronized order-book source over HTTP. Locally, the REST API listens on port &lt;code&gt;42081&lt;/code&gt;; in Kubernetes, it is exposed through the &lt;code&gt;ubdcc-restapi&lt;/code&gt; service, usually on port &lt;code&gt;80&lt;/code&gt; behind a LoadBalancer.&lt;/p&gt;

&lt;p&gt;Quick local start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;ubdcc
ubdcc start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create redundant DepthCaches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s1"&gt;'http://127.0.0.1:42081/create_depthcaches'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"exchange": "binance.com", "markets": ["BTCUSDC", "ETHUSDT", "BNBUSDC"], "desired_quantity": 2}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Query the order book via REST:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s1"&gt;'http://127.0.0.1:42081/get_asks?exchange=binance.com&amp;amp;market=BTCUSDC&amp;amp;limit_count=5'&lt;/span&gt;
curl &lt;span class="s1"&gt;'http://127.0.0.1:42081/get_bids?exchange=binance.com&amp;amp;market=BTCUSDC&amp;amp;limit_count=5'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important point is the URL shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/get_asks?exchange=binance.com&amp;amp;market=BTCUSDC&amp;amp;limit_count=5
/get_bids?exchange=binance.com&amp;amp;market=BTCUSDC&amp;amp;threshold_volume=100000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;UBDCC exposes synchronized order-book data through explicit REST endpoints such as &lt;code&gt;/get_asks&lt;/code&gt; and &lt;code&gt;/get_bids&lt;/code&gt;. For normal consumers, the response stays focused on the requested book side. When you need operational details, &lt;code&gt;debug=true&lt;/code&gt; adds routing and timing metadata.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s1"&gt;'http://127.0.0.1:42081/get_asks?exchange=binance.com&amp;amp;market=BTCUSDC&amp;amp;limit_count=2&amp;amp;debug=true'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;UBDCC consists of three component types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;mgmt&lt;/strong&gt; on &lt;code&gt;42080&lt;/code&gt;: cluster state and DepthCache distribution,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;restapi&lt;/strong&gt; on &lt;code&gt;42081&lt;/code&gt; locally / &lt;code&gt;80&lt;/code&gt; in Kubernetes: the endpoint your clients call,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;DCN&lt;/strong&gt; processes from &lt;code&gt;42082&lt;/code&gt; upward: the workers running the actual UBLDC DepthCaches.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your client calls &lt;code&gt;restapi&lt;/code&gt;. It routes reads to the responsible DCN and management operations to &lt;code&gt;mgmt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Two posts dive into UBDCC in detail:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://blog.technopathy.club/ubdcc-deep-dive-building-a-trust-layer-for-binance-order-books" rel="noopener noreferrer"&gt;UBDCC Deep-Dive: Building a Trust Layer for Binance Order Books&lt;/a&gt; — architecture, why it exists, what problem it solves.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://blog.technopathy.club/from-pip-install-to-a-redundant-binance-order-book-cluster-ubdcc-dashboard-quickstart" rel="noopener noreferrer"&gt;From &lt;code&gt;pip install&lt;/code&gt; to a Redundant Binance Order Book Cluster — UBDCC + Dashboard Quickstart&lt;/a&gt; — a working cluster in minutes.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi87m3za33kd76g36s6q3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi87m3za33kd76g36s6q3.png" alt="UBDCC Dashboard" width="800" height="727"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What "Production-Grade" Actually Means
&lt;/h2&gt;

&lt;p&gt;Most articles compare libraries on &lt;strong&gt;what they do&lt;/strong&gt;. The ones that decide whether your bot survives a year compare on &lt;strong&gt;what they handle when things go wrong&lt;/strong&gt;. Here's the practical checklist that separates a weekend project from a service:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concern&lt;/th&gt;
&lt;th&gt;python-binance&lt;/th&gt;
&lt;th&gt;binance-connector / official SDKs&lt;/th&gt;
&lt;th&gt;CCXT&lt;/th&gt;
&lt;th&gt;UBS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Automatic WebSocket reconnect&lt;/td&gt;
&lt;td&gt;Partial / manager-dependent&lt;/td&gt;
&lt;td&gt;Low-level / DIY&lt;/td&gt;
&lt;td&gt;Available in CCXT Pro / exchange-dependent&lt;/td&gt;
&lt;td&gt;Yes, managed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WebSocket API trading requests&lt;/td&gt;
&lt;td&gt;Recent support&lt;/td&gt;
&lt;td&gt;Yes, official SDK direction&lt;/td&gt;
&lt;td&gt;Exchange-dependent / Pro&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes — integrated into UBWA manager&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request-specific WS API callbacks&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;SDK-style / DIY routing&lt;/td&gt;
&lt;td&gt;Abstraction-dependent&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multiple WS API streams&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;DIY&lt;/td&gt;
&lt;td&gt;Abstraction-dependent&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes — via stream IDs / labels&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WebSocket sequence-gap detection (depth)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (validates &lt;code&gt;U&lt;/code&gt;/&lt;code&gt;u&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explicit out-of-sync error surface&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Yes —&lt;/strong&gt; &lt;code&gt;DepthCacheOutOfSync&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Orphaned depth-level pruning&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User-data listenKey auto-renew&lt;/td&gt;
&lt;td&gt;Yes (less robust)&lt;/td&gt;
&lt;td&gt;DIY&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Yes, robust&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Subscribe at runtime without reconnect&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Native asyncio &lt;code&gt;await&lt;/code&gt; queue&lt;/td&gt;
&lt;td&gt;No decoupled backpressure-aware queue&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Mixed&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-account routing&lt;/td&gt;
&lt;td&gt;Separate clients / application routing&lt;/td&gt;
&lt;td&gt;Separate clients / application routing&lt;/td&gt;
&lt;td&gt;Separate clients / abstraction-dependent&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Integrated routing via stream IDs / labels&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Native/Cython components with multi-arch wheels&lt;/td&gt;
&lt;td&gt;N/A / mostly pure Python&lt;/td&gt;
&lt;td&gt;N/A / mostly pure Python&lt;/td&gt;
&lt;td&gt;N/A / pure Python&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes — x86_64, aarch64, arm64&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Connection-state observability&lt;/td&gt;
&lt;td&gt;Limited / application-level&lt;/td&gt;
&lt;td&gt;Low-level / application-level&lt;/td&gt;
&lt;td&gt;Exchange-dependent&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Built-in: stream lifecycle signals, per-stream labels, reconnect visibility, and stream-scoped log context&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cluster-scale option&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;UBDCC&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is not only for large desks. Beginners benefit from stable defaults too. A small bot is not better because its WebSocket handling is fragile, and &lt;code&gt;python-binance&lt;/code&gt; is not automatically simpler just because it ranks first. The practical reason to look at UBS is that it makes many failure modes explicit before they become your problem.&lt;/p&gt;




&lt;h3&gt;
  
  
  Trust and installation notes
&lt;/h3&gt;

&lt;p&gt;UNICORN Binance Suite is MIT-licensed open source. Install from PyPI or the official GitHub repositories under &lt;code&gt;oliver-zehentleitner&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There have been fraudulent repositories impersonating UBWA with malware payloads, so avoid random forks, ZIP downloads, or similarly named projects. The official package names are listed above.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where to Go from Here
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Build something:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Stream market data → Kafka, ready for downstream processing: &lt;a href="https://blog.technopathy.club/passing-binance-market-data-to-apache-kafka-in-python-with-aiokafka" rel="noopener noreferrer"&gt;Passing Binance Market Data to Apache Kafka in Python with aiokafka&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Atomic OCO take-profit + stop-loss: &lt;a href="https://blog.technopathy.club/buy-an-asset-and-instantly-create-a-take-profit-and-stop-loss-oco-sell-order-using-python-in-binance-isolated-margin" rel="noopener noreferrer"&gt;Buy an Asset and Instantly Create a Take-Profit + Stop-Loss OCO Sell Order&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run a redundant order-book cluster: &lt;a href="https://blog.technopathy.club/from-pip-install-to-a-redundant-binance-order-book-cluster-ubdcc-dashboard-quickstart" rel="noopener noreferrer"&gt;UBDCC + Dashboard Quickstart&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Understand the internals:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Why a "correct" depth cache rots if you follow the spec: &lt;a href="https://blog.technopathy.club/your-binance-depthcache-is-rotting-here-s-the-proof-in-25-hours" rel="noopener noreferrer"&gt;Your Binance DepthCache Is Rotting&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What UBDCC actually does, architecturally: &lt;a href="https://blog.technopathy.club/ubdcc-deep-dive-building-a-trust-layer-for-binance-order-books" rel="noopener noreferrer"&gt;UBDCC Deep-Dive&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Binance API security model and where it leaks: &lt;a href="https://blog.technopathy.club/binance-fixed-the-ip-whitelist-gap-the-disclosure-process-is-still-broken" rel="noopener noreferrer"&gt;Binance Fixed the IP Whitelist Gap. The Disclosure Process Is Still Broken&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Docs and source:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;UBWA: &lt;a href="https://oliver-zehentleitner.github.io/unicorn-binance-websocket-api" rel="noopener noreferrer"&gt;docs&lt;/a&gt; · &lt;a href="https://github.com/oliver-zehentleitner/unicorn-binance-websocket-api" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;UBRA: &lt;a href="https://oliver-zehentleitner.github.io/unicorn-binance-rest-api" rel="noopener noreferrer"&gt;docs&lt;/a&gt; · &lt;a href="https://github.com/oliver-zehentleitner/unicorn-binance-rest-api" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;UBLDC: &lt;a href="https://oliver-zehentleitner.github.io/unicorn-binance-local-depth-cache" rel="noopener noreferrer"&gt;docs&lt;/a&gt; · &lt;a href="https://github.com/oliver-zehentleitner/unicorn-binance-local-depth-cache" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;UBTSL: &lt;a href="https://oliver-zehentleitner.github.io/unicorn-binance-trailing-stop-loss" rel="noopener noreferrer"&gt;docs&lt;/a&gt; · &lt;a href="https://github.com/oliver-zehentleitner/unicorn-binance-trailing-stop-loss" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;UnicornFy: &lt;a href="https://oliver-zehentleitner.github.io/unicorn-fy" rel="noopener noreferrer"&gt;docs&lt;/a&gt; · &lt;a href="https://github.com/oliver-zehentleitner/unicorn-fy" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;UBDCC: &lt;a href="https://oliver-zehentleitner.github.io/unicorn-binance-depth-cache-cluster/" rel="noopener noreferrer"&gt;docs&lt;/a&gt; · &lt;a href="https://github.com/oliver-zehentleitner/unicorn-binance-depth-cache-cluster" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Suite meta-package: &lt;a href="https://oliver-zehentleitner.github.io/unicorn-binance-suite" rel="noopener noreferrer"&gt;docs&lt;/a&gt; · &lt;a href="https://github.com/oliver-zehentleitner/unicorn-binance-suite" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Talk to humans:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Telegram: &lt;a href="https://t.me/unicorndevs" rel="noopener noreferrer"&gt;&lt;strong&gt;t.me/unicorndevs&lt;/strong&gt;&lt;/a&gt; — the answer to most questions is one message away.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;GitHub Discussions on any of the repos above.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;python-binance&lt;/code&gt;, the official Binance SDKs, and &lt;code&gt;CCXT&lt;/code&gt; are useful tools. They exist for real reasons. But UBS is built around a different premise: a Binance bot should not only be able to call endpoints — it should understand stream lifecycle, reconnects, order-book trust, out-of-sync states, WebSocket API routing, and failure surfaces from the beginning.&lt;/p&gt;

&lt;p&gt;That is useful for production systems, but it is also useful for beginners. Starting small does not mean starting fragile. A stable library is not overkill just because your first script has 50 lines.&lt;/p&gt;

&lt;p&gt;If you are building anything Binance-specific in Python, try the suite once. The install is one line. The list of things you no longer have to reinvent is the rest of this article.&lt;/p&gt;

&lt;p&gt;Either way: name the failure modes before you ship. The libraries that make those states visible are the ones you want under your code.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This guide will be expanded into a series of deep-dives — WebSocket API request routing, WebSocket reconnect internals,&lt;/em&gt; &lt;code&gt;stream_signals&lt;/code&gt;&lt;em&gt;, the&lt;/em&gt; &lt;code&gt;high_performance&lt;/code&gt; &lt;em&gt;flag, OCO order patterns, UBDCC cluster architecture, and more. Subscribe to the&lt;/em&gt; &lt;a href="https://blog.technopathy.club/series/unicorn-binance-suite" rel="noopener noreferrer"&gt;&lt;em&gt;UNICORN Binance Suite&lt;/em&gt;&lt;/a&gt; &lt;em&gt;series to catch each one as it lands.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;I hope you found this informative and useful.&lt;/p&gt;

&lt;p&gt;Follow me on &lt;a href="https://www.binance.com/en/square/profile/oliver-zehentleitner" rel="noopener noreferrer"&gt;Binance Square&lt;/a&gt;, &lt;a href="https://github.com/oliver-zehentleitner" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, &lt;a href="https://burningboard.net/@oliverzehentleitner" rel="noopener noreferrer"&gt;Mastodon&lt;/a&gt;, &lt;a href="https://x.com/unicorn_oz" rel="noopener noreferrer"&gt;X&lt;/a&gt;, and &lt;a href="https://www.linkedin.com/in/oliver-zehentleitner/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, or join &lt;a href="https://t.me/unicorndevs" rel="noopener noreferrer"&gt;Telegram&lt;/a&gt; for updates on my latest publications. Constructive feedback is always appreciated.&lt;/p&gt;

&lt;p&gt;Thank you for reading, and happy coding! ¯\_(ツ)_/¯&lt;/p&gt;

</description>
      <category>python</category>
      <category>cryptocurrency</category>
      <category>api</category>
    </item>
    <item>
      <title>Keep the Why: Code Becomes Legacy When Nobody Remembers Why</title>
      <dc:creator>Oliver Zehentleitner</dc:creator>
      <pubDate>Sat, 11 Jul 2026 06:47:39 +0000</pubDate>
      <link>https://dev.to/oliverzehentleitner/keep-the-why-code-becomes-legacy-when-nobody-remembers-why-47jp</link>
      <guid>https://dev.to/oliverzehentleitner/keep-the-why-code-becomes-legacy-when-nobody-remembers-why-47jp</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Keep a Changelog records what changed. Keep the Why preserves why it changed.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;I turned four months of working with AI coding agents into an open-source skill for preserving the reasoning behind a codebase.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I've been maintaining the &lt;a href="https://blog.technopathy.club/page/unicorn-binance-suite" rel="noopener noreferrer"&gt;UNICORN Binance Suite&lt;/a&gt; since 2019. It is a set of open-source Python packages for building automated trading systems on Binance, and parts of it have been running in production for years.&lt;/p&gt;

&lt;p&gt;For the last few months, I have also been working with an AI coding agent on these projects every day.&lt;/p&gt;

&lt;p&gt;Early on, I invested quite a bit of time into the harness around it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;conventions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;project structure&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;persistent context&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;documentation the agent could actually use&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;rules for how it should work inside a mature codebase&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That effort paid off.&lt;/p&gt;

&lt;p&gt;Today I can point the agent at a piece of code and ask why something looks the way it does. In many cases, it can answer correctly without guessing.&lt;/p&gt;

&lt;p&gt;Not because the model somehow remembers seven years of development history. It does not.&lt;/p&gt;

&lt;p&gt;It can answer because the reasoning is still present in the repository.&lt;/p&gt;

&lt;p&gt;That distinction matters.&lt;/p&gt;

&lt;p&gt;The more I worked with this setup, the more obvious something became:&lt;/p&gt;

&lt;p&gt;If structured context makes an AI agent useful in a codebase it did not grow up with, the same context should also help a human developer entering that project later.&lt;/p&gt;

&lt;p&gt;A new developer should not have to reverse-engineer every strange retry loop, compatibility workaround or architectural boundary from the code alone. They should be able to ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Why is this retry logic so defensive?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Why are we not using the obvious library?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Why does this component wait for a snapshot before processing buffered events?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What broke the last time somebody simplified this?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And ideally, the answer should already live with the project.&lt;/p&gt;

&lt;p&gt;That is the idea behind &lt;strong&gt;Keep the Why&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Tests preserve expected behaviour.&lt;/p&gt;

&lt;p&gt;Git preserves changes.&lt;/p&gt;

&lt;p&gt;Issue trackers preserve discussions, sometimes.&lt;/p&gt;

&lt;p&gt;What often disappears is the reasoning behind the final implementation.&lt;/p&gt;

&lt;p&gt;That creates a few very familiar problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Re-debate
&lt;/h3&gt;

&lt;p&gt;A team discusses the same architecture question again because nobody remembers that it was already settled eighteen months ago.&lt;/p&gt;

&lt;p&gt;The previous decision may still be correct. The reasons are simply gone.&lt;/p&gt;

&lt;h3&gt;
  
  
  Silent regression
&lt;/h3&gt;

&lt;p&gt;Somebody finds a workaround that looks unnecessary and cleans it up.&lt;/p&gt;

&lt;p&gt;Unfortunately, it was not unnecessary. It was the fix for a bug nobody documented properly.&lt;/p&gt;

&lt;p&gt;The code looked ugly because reality was ugly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Onboarding stall
&lt;/h3&gt;

&lt;p&gt;A new developer sees code they do not understand and avoids touching it.&lt;/p&gt;

&lt;p&gt;That is usually a reasonable decision. Changing unfamiliar code without context is how repeat incidents are born.&lt;/p&gt;

&lt;p&gt;AI coding agents have the same problem, only faster. A fresh session starts with little or no project history. Without preserved rationale, the agent either spends time reconstructing it or confidently proposes the exact approach that already failed once.&lt;/p&gt;

&lt;h2&gt;
  
  
  This is not a new problem
&lt;/h2&gt;

&lt;p&gt;Architecture Decision Records have existed for years, and they are useful.&lt;/p&gt;

&lt;p&gt;But they require somebody to notice that a decision deserves an ADR, stop the current work and document it deliberately. Large architectural choices may get that treatment. Smaller constraints often do not.&lt;/p&gt;

&lt;p&gt;Those smaller decisions are frequently the ones that become dangerous later:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;a defensive timeout&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;an odd startup sequence&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;a compatibility branch&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;a strange cache invalidation rule&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;a library that was evaluated and rejected&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;a workaround linked to a production incident&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They are too small for ceremony and too important to forget.&lt;/p&gt;

&lt;p&gt;The important change is not that reasoning suddenly became valuable.&lt;/p&gt;

&lt;p&gt;The change is that an AI agent already participates in many of the conversations where that reasoning appears. Capturing it no longer has to be a completely separate documentation task.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Keep the Why is
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://keepthewhy.com/" rel="noopener noreferrer"&gt;Keep the Why&lt;/a&gt; is an open-source agent skill.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fuploads%2Fcovers%2F69d4b99a5da14bc70e00d4f6%2F066f1c95-1ec4-48ab-bd8a-96b39d3ca8c6.png%2520align%3D" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fuploads%2Fcovers%2F69d4b99a5da14bc70e00d4f6%2F066f1c95-1ec4-48ab-bd8a-96b39d3ca8c6.png%2520align%3D" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is a &lt;code&gt;SKILL.md&lt;/code&gt; file with supporting references and examples, based on the open Agent Skills format. There is no service, database, account or MCP server behind it.&lt;/p&gt;

&lt;p&gt;It teaches a coding agent four related workflows.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Continuous capture
&lt;/h3&gt;

&lt;p&gt;During normal development, the agent notices when a conversation contains useful rationale:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;an architectural choice&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;a rejected alternative&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;a production constraint&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;an incident finding&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;an intentional workaround&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;a behaviour that looks strange but is deliberate&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It then updates the relevant project context at a natural checkpoint.&lt;/p&gt;

&lt;p&gt;The goal is not to document every line of code. That would just create a second codebase, only written in Markdown.&lt;/p&gt;

&lt;p&gt;The goal is to preserve the parts that are not obvious from the implementation itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Retrospective recovery
&lt;/h3&gt;

&lt;p&gt;The skill can also be applied to an existing repository.&lt;/p&gt;

&lt;p&gt;The agent inspects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;code&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;git history&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;issues&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;pull requests&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;existing documentation&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It reconstructs what the available evidence supports and clearly marks what remains uncertain.&lt;/p&gt;

&lt;p&gt;It should not invent a clean historical narrative just because that would look nice in a document.&lt;/p&gt;

&lt;p&gt;Sometimes the honest result is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We know what this does. We do not yet know why it was designed this way.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is useful information too.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Knowledge-transfer interviews
&lt;/h3&gt;

&lt;p&gt;This is the part I find especially interesting.&lt;/p&gt;

&lt;p&gt;Before a long-term maintainer changes teams, leaves or retires, the agent can first analyse the repository and identify areas where the reasoning is missing.&lt;/p&gt;

&lt;p&gt;It can then prepare focused questions:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why does this synchronization process wait for the snapshot before applying buffered updates?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;instead of:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Please explain the synchronization system.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The first question has a chance of recovering useful knowledge. The second usually produces a meeting nobody wants to attend.&lt;/p&gt;

&lt;p&gt;For broad, mostly tacit knowledge, the process can also work the other way around: let the maintainer explain the history in their own words, then extract decisions, constraints and open questions from the conversation.&lt;/p&gt;

&lt;p&gt;The important part is that the result becomes project knowledge, not another recording nobody will ever watch again.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Maintenance
&lt;/h3&gt;

&lt;p&gt;Documentation can become legacy too.&lt;/p&gt;

&lt;p&gt;So the skill also defines how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;update existing topic files&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;resolve contradictions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;mark old decisions as superseded&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;split files before they become too large&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;keep indexes lean enough for humans and agents&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;avoid creating five documents for one small decision&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is living documentation, not an archive of frozen decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The structure
&lt;/h2&gt;

&lt;p&gt;Keep the Why deliberately separates two kinds of project knowledge.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docs/
context/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;docs/&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How do I use, operate, test, deploy or troubleshoot this project?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;installation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;configuration&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;API usage&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;testing&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;deployment&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;operations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;troubleshooting&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;context/&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why is the project built this way?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;architectural rationale&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;rejected alternatives&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;historical constraints&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;incident learnings&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;compatibility decisions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;deliberate workarounds&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;superseded approaches&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both layers are plain Markdown and versioned with the repository.&lt;/p&gt;

&lt;p&gt;There is no separate AI-only knowledge store waiting to drift away from the human documentation.&lt;/p&gt;

&lt;p&gt;The context is organised by topic, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;context/
├── index.md
├── architecture.md
├── synchronization.md
├── compatibility.md
└── incidents.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I prefer this over a file-by-file shadow tree because decisions often span several files and survive refactors.&lt;/p&gt;

&lt;p&gt;I also prefer it over one file per decision for smaller, evolving topics. Formal ADRs still make sense where they fit. Keep the Why is not trying to ban them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it looks like in practice
&lt;/h2&gt;

&lt;p&gt;Imagine a normal implementation discussion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You: The simpler retry mechanism caused duplicate orders during
     reconnects. Keep the stateful version even though it looks
     more complicated.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is not just an implementation detail.&lt;/p&gt;

&lt;p&gt;It contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;the rejected alternative&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the reason it was rejected&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the failure mode&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the constraint future maintainers must preserve&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The agent should recognise that and update the relevant topic in &lt;code&gt;context/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Six months later, somebody finds the code and thinks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This looks over-engineered. I can simplify it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Chesterton's Fence, except this time the fence has a sign on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule that matters most
&lt;/h2&gt;

&lt;p&gt;The core rule of Keep the Why is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Never invent rationale.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Plausible-sounding historical explanations are worse than missing documentation. A blank page creates caution. A confident false explanation creates bad decisions.&lt;/p&gt;

&lt;p&gt;Each relevant rationale entry should make clear whether its evidence is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;confirmed&lt;/strong&gt; — supported by a maintainer or authoritative project evidence&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;inferred&lt;/strong&gt; — reasonably derived, but not confirmed&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;unknown&lt;/strong&gt; — the available evidence is insufficient&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Separately, an entry can be marked as &lt;strong&gt;superseded&lt;/strong&gt; when it was once valid but has since been replaced.&lt;/p&gt;

&lt;p&gt;This distinction matters: evidence confidence and temporal status are two different things.&lt;/p&gt;

&lt;p&gt;This is not decoration.&lt;/p&gt;

&lt;p&gt;It is the difference between useful project knowledge and hallucination wearing documentation's clothes.&lt;/p&gt;

&lt;p&gt;The second important rule is proportionality.&lt;/p&gt;

&lt;p&gt;A self-explanatory decision may need one sentence.&lt;/p&gt;

&lt;p&gt;A surprising architecture boundary may need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;context&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;decision&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;alternatives&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;consequences&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;evidence&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;conditions under which it should be revisited&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not every decision deserves a ceremony. Not every strange-looking line deserves to be forgotten either.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it relates to existing approaches
&lt;/h2&gt;

&lt;p&gt;Keep the Why is not a replacement for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;tests&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;README files&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;CONTRIBUTING.md&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ADRs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;issue trackers&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://keepachangelog.com/" rel="noopener noreferrer"&gt;Keep a Changelog&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The name is deliberately related to Keep a Changelog.&lt;/p&gt;

&lt;p&gt;Keep a Changelog records &lt;strong&gt;what changed&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Keep the Why preserves &lt;strong&gt;why it changed&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There is also prior work in this space:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Architecture Decision Records&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/hexapode/git-why" rel="noopener noreferrer"&gt;git-why&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/addyosmani/agent-skills/blob/main/skills/documentation-and-adrs/SKILL.md" rel="noopener noreferrer"&gt;Addy Osmani's &lt;code&gt;documentation-and-adrs&lt;/code&gt; skill&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/me2resh/agent-decision-record" rel="noopener noreferrer"&gt;Agent Decision Records (AgDR)&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;repository-memory and agent-context tools&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I did not invent the idea that software decisions should be documented.&lt;/p&gt;

&lt;p&gt;What I wanted was a practical combination of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;continuous capture&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;retrospective recovery&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;code-guided knowledge-transfer interviews&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;topic-based living documentation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;repository-native storage&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;no required external platform&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That combination grew out of how I was already working.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the project stands
&lt;/h2&gt;

&lt;p&gt;The underlying practice is not completely new to me.&lt;/p&gt;

&lt;p&gt;I have been testing variations of this approach in my own AI-assisted development workflow for roughly four months.&lt;/p&gt;

&lt;p&gt;But the public project is not a copy of that private setup.&lt;/p&gt;

&lt;p&gt;It is a new incarnation of it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;cleaned up&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;generalised&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;made cross-agent&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;turned into an explicit method&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;separated from my own repository conventions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;packaged as an open-source skill&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means it now has to prove itself in real use — and mature through it.&lt;/p&gt;

&lt;p&gt;The initial structure looks reasonable to me. The real test is what happens across different repositories, teams and agents.&lt;/p&gt;

&lt;p&gt;I expect some parts to change.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;When does the agent capture too much?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When does it ask too little?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Which structure works for a small library and which one works for a large multi-repository system?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How much classification is useful before it becomes bureaucracy?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When should a topic be split?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How do we keep old rationale from becoming trusted but stale?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is also a small but relevant body of supporting research. &lt;a href="https://arxiv.org/abs/2601.21116" rel="noopener noreferrer"&gt;A 2026 position paper&lt;/a&gt; audited 62 architectural decisions across two internal projects and found that roughly 23% had stale supporting evidence within two months. The sample is small and the result is not a universal law, but it supports something maintainers already know: documentation does not stay correct by existing. It has to be maintained.&lt;/p&gt;

&lt;p&gt;I do not yet have automated cross-agent eval results or a published before-and-after study.&lt;/p&gt;

&lt;p&gt;The eval cases exist. The public evidence does not, yet.&lt;/p&gt;

&lt;p&gt;That is the honest state of the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Install it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add oliver-zehentleitner/keep-the-why
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or inspect and install it through GitHub:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh skill preview oliver-zehentleitner/keep-the-why keep-the-why
gh skill &lt;span class="nb"&gt;install &lt;/span&gt;oliver-zehentleitner/keep-the-why keep-the-why
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is instructions only:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;no scripts&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;no network calls of its own&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;no external service&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;no account&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;no database&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Documentation and manual installation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://keepthewhy.com/" rel="noopener noreferrer"&gt;keepthewhy.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source, license and the actual skill:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/oliver-zehentleitner/keep-the-why" rel="noopener noreferrer"&gt;github.com/oliver-zehentleitner/keep-the-why&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Feedback is welcome
&lt;/h2&gt;

&lt;p&gt;Keep the Why is young.&lt;/p&gt;

&lt;p&gt;The base is there, but the method now needs real use outside my own projects.&lt;/p&gt;

&lt;p&gt;I am especially interested in feedback from people working with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;mature codebases&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;long-running production systems&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AI coding agents&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;developer onboarding&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;knowledge transfer&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;maintainers who have inherited code nobody wants to touch&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bug reports are useful.&lt;/p&gt;

&lt;p&gt;Conceptual criticism is even more useful.&lt;/p&gt;

&lt;p&gt;If the structure is wrong for your project, I want to know why. If an important workflow is missing, propose it. If another project already solves part of this better, point me to it.&lt;/p&gt;

&lt;p&gt;The project is open source because I do not think this should become another private knowledge silo.&lt;/p&gt;

&lt;p&gt;The whole point is to keep the reasoning with the code.&lt;/p&gt;

&lt;p&gt;Because “ask Bob” is not documentation.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>documentation</category>
      <category>agents</category>
      <category>legacy</category>
    </item>
    <item>
      <title>Your Binance L2 Order Book Can Be Gap-Free and Still Be Wrong</title>
      <dc:creator>Oliver Zehentleitner</dc:creator>
      <pubDate>Thu, 11 Jun 2026 11:51:41 +0000</pubDate>
      <link>https://dev.to/oliverzehentleitner/your-binance-l2-order-book-can-be-gap-free-and-still-be-wrong-1kk8</link>
      <guid>https://dev.to/oliverzehentleitner/your-binance-l2-order-book-can-be-gap-free-and-still-be-wrong-1kk8</guid>
      <description>&lt;p&gt;A Binance order book looks simple until you try to keep one correct for hours, days, or weeks.&lt;/p&gt;

&lt;p&gt;At first, the task appears straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;subscribe to Binance depth updates over WebSocket,&lt;/li&gt;
&lt;li&gt;fetch an order book snapshot over REST,&lt;/li&gt;
&lt;li&gt;apply every update in sequence,&lt;/li&gt;
&lt;li&gt;expose the resulting bids and asks to your strategy.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is enough to produce something that &lt;em&gt;looks&lt;/em&gt; like a live Binance L2 order book.&lt;/p&gt;

&lt;p&gt;It is not enough to prove that the book is correct.&lt;/p&gt;

&lt;p&gt;A local order book can have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no detected sequence gap,&lt;/li&gt;
&lt;li&gt;a healthy WebSocket connection,&lt;/li&gt;
&lt;li&gt;plausible best bids and asks,&lt;/li&gt;
&lt;li&gt;valid-looking quantities,&lt;/li&gt;
&lt;li&gt;and thousands of price levels that no longer exist on Binance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This article covers the full lifecycle of a trustworthy Binance order book: initialization, gap detection, retention, resynchronization, observability, redundancy, and the point where an in-process cache should become shared infrastructure.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A wrong order book is worse than no order book. No data stops you. Bad data lies to you.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1nowqvjf4ner9jqgt6uj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1nowqvjf4ner9jqgt6uj.png" alt=" " width="800" height="727"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a Binance L2 order book?
&lt;/h2&gt;

&lt;p&gt;A Level 2, or L2, order book represents aggregated liquidity at individual price levels.&lt;/p&gt;

&lt;p&gt;For each side of the market, it contains pairs such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;price, quantity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bids"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"104250.10"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.842"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"104250.00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.307"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"asks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"104250.20"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.514"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"104250.30"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.191"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The highest bid and lowest ask form the top of book.&lt;/p&gt;

&lt;p&gt;The full price ladder is useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;spread and slippage calculations,&lt;/li&gt;
&lt;li&gt;market-depth analysis,&lt;/li&gt;
&lt;li&gt;liquidity monitoring,&lt;/li&gt;
&lt;li&gt;order-flow research,&lt;/li&gt;
&lt;li&gt;market making,&lt;/li&gt;
&lt;li&gt;arbitrage,&lt;/li&gt;
&lt;li&gt;execution planning,&lt;/li&gt;
&lt;li&gt;and trading-system risk controls.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Binance does not continuously send a complete order book to every client. It gives you separate pieces that your application must combine.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Binance actually gives you
&lt;/h2&gt;

&lt;p&gt;To construct a local Binance order book, you normally use:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;a REST depth snapshot,&lt;/li&gt;
&lt;li&gt;a WebSocket diff-depth stream.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The REST response gives you a bounded view of the order book at a specific &lt;code&gt;lastUpdateId&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The WebSocket stream then sends incremental changes containing fields such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;U  first update ID in the event
u  final update ID in the event
pu previous event's final update ID on relevant futures streams
b  bid updates
a  ask updates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your code is responsible for turning those two independent data sources into one coherent state.&lt;/p&gt;

&lt;p&gt;That requires more than applying JSON messages to a dictionary.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first trap: the snapshot race condition
&lt;/h2&gt;

&lt;p&gt;A common implementation does this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fetch snapshot
connect WebSocket
apply updates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That ordering is unsafe.&lt;/p&gt;

&lt;p&gt;Updates can occur after the snapshot was created but before the WebSocket subscription becomes active. Those updates are lost, and the local Binance order book starts with a silent gap.&lt;/p&gt;

&lt;p&gt;The safer sequence is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;connect WebSocket
buffer depth events
fetch REST snapshot
discard obsolete buffered events
find the first event that continues from the snapshot
replay the remaining buffer
continue with live updates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nb"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="nf"&gt;start_depth_stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;on_event&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;snapshot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fetch_depth_snapshot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;last_update_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lastUpdateId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nb"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;event&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;buffer&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;u&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;last_update_id&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;first_event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;find_first_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;U&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;last_update_id&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;u&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;apply_snapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;first_event&lt;/span&gt;&lt;span class="p"&gt;:]:&lt;/span&gt;
    &lt;span class="nf"&gt;apply_depth_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact protocol differs slightly between Binance Spot and Futures, so do not blindly reuse a Spot implementation for Futures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sequence continuity is a trust boundary
&lt;/h2&gt;

&lt;p&gt;Once the Binance order book is synchronized, every event must continue the sequence you already processed.&lt;/p&gt;

&lt;p&gt;For Spot, the steady-state check is commonly based on the next expected update ID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;U&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;last_update_id&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;request_full_resync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For relevant Futures streams, Binance provides &lt;code&gt;pu&lt;/code&gt;, which references the previous event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pu&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;last_update_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;request_full_resync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After validation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;apply_updates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;last_update_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;u&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A broken sequence is not merely a warning.&lt;/p&gt;

&lt;p&gt;It means your local order book no longer has a proven relationship to the exchange state.&lt;/p&gt;

&lt;p&gt;Do not log the problem and keep serving the same book. Mark it as out of sync, stop trusting it, and initialize it again from a fresh snapshot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Applying bid and ask updates
&lt;/h2&gt;

&lt;p&gt;Each depth event contains price-level changes.&lt;/p&gt;

&lt;p&gt;The basic rule is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;apply_level&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;quantity&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;quantity&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This part is easy.&lt;/p&gt;

&lt;p&gt;The dangerous assumption is that correct update application plus correct sequence validation guarantees a correct long-running Binance order book.&lt;/p&gt;

&lt;p&gt;It does not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The second trap: a gap-free order book can still rot
&lt;/h2&gt;

&lt;p&gt;While maintaining the UNICORN Binance Local Depth Cache, I investigated a &lt;a href="https://github.com/oliver-zehentleitner/unicorn-binance-local-depth-cache/issues/45" rel="noopener noreferrer"&gt;report that bids and asks kept growing beyond their expected size&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The implementation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;started from a valid snapshot,&lt;/li&gt;
&lt;li&gt;processed updates in order,&lt;/li&gt;
&lt;li&gt;removed levels when Binance sent quantity &lt;code&gt;0&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;and detected broken update continuity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It still accumulated stale price levels.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;A bounded REST snapshot defines the initial view your cache can validate. The diff stream can later introduce price levels outside the active region that your application intends to maintain.&lt;/p&gt;

&lt;p&gt;A naive implementation inserts every streamed level and removes it only when a future update explicitly sets its quantity to zero.&lt;/p&gt;

&lt;p&gt;But a reliable cleanup event does not necessarily arrive for every price level your cache has collected. Levels can move outside the bounded region you are validating and remain in local memory indefinitely.&lt;/p&gt;

&lt;p&gt;The result is a growing archive of plausible-looking historical price levels.&lt;/p&gt;

&lt;p&gt;I call them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;orphaned levels,&lt;/li&gt;
&lt;li&gt;ghost levels,&lt;/li&gt;
&lt;li&gt;or ghost orders.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They are especially dangerous because they do not look corrupted.&lt;/p&gt;

&lt;p&gt;A stale level such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;104000.00 BTCUSDT bid quantity 0.73
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;looks completely reasonable.&lt;/p&gt;

&lt;p&gt;Your best bid and best ask may still be correct while deeper liquidity, cumulative volume, order-book shape, and slippage estimates are increasingly wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a 25-hour Binance order book test showed
&lt;/h2&gt;

&lt;p&gt;To isolate this behavior, I ran two BTCUSDT depth caches side by side for 25.10 hours.&lt;/p&gt;

&lt;p&gt;Both received the same WebSocket data and were audited against REST snapshots at the same times.&lt;/p&gt;

&lt;p&gt;The only relevant difference was retention:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;naive cache&lt;/strong&gt;: apply updates and delete only on &lt;code&gt;quantity == 0&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;fixed cache&lt;/strong&gt;: apply the same updates but actively prune the maintained book back to its intended top-1000 corridor.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;BTC moved only 1.88% during the test window. This was a calm market, not a flash crash or extreme volatility event.&lt;/p&gt;

&lt;p&gt;At the final audit:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Implementation&lt;/th&gt;
&lt;th&gt;Local bids&lt;/th&gt;
&lt;th&gt;Local asks&lt;/th&gt;
&lt;th&gt;Bid levels matching REST&lt;/th&gt;
&lt;th&gt;Ask levels matching REST&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Naive&lt;/td&gt;
&lt;td&gt;20,758&lt;/td&gt;
&lt;td&gt;9,116&lt;/td&gt;
&lt;td&gt;24.09%&lt;/td&gt;
&lt;td&gt;39.82%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pruned&lt;/td&gt;
&lt;td&gt;1,011&lt;/td&gt;
&lt;td&gt;1,078&lt;/td&gt;
&lt;td&gt;87.83%&lt;/td&gt;
&lt;td&gt;91.74%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The naive Binance order book started almost perfectly aligned.&lt;/p&gt;

&lt;p&gt;Then it rotted.&lt;br&gt;
&lt;a href="https://oliver-zehentleitner.github.io/binance-depthcache-forensics/report_naive.html" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuz88ak5kfokxsphu2c64.png" alt=" " width="799" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open interactive 3D chart: &lt;a href="https://oliver-zehentleitner.github.io/binance-depthcache-forensics/report_naive.html" rel="noopener noreferrer"&gt;report_naive.html&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Warning: large Plotly file, about 78 MB.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://oliver-zehentleitner.github.io/binance-depthcache-forensics/report_fixed.html" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flx1raw2nzctgxj6uueza.png" alt=" " width="799" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open interactive 3D chart: &lt;a href="https://oliver-zehentleitner.github.io/binance-depthcache-forensics/report_fixed.html" rel="noopener noreferrer"&gt;report_fixed.html&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Warning: large Plotly file, about 41 MB.&lt;/p&gt;

&lt;p&gt;The important lesson is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Gap-free does not mean correct forever.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sequence checks answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Did I miss a depth event?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A retention policy answers a different question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which price levels am I still able and willing to claim as part of my maintained L2 order book?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A production implementation needs both.&lt;/p&gt;
&lt;h2&gt;
  
  
  Add an explicit retention policy
&lt;/h2&gt;

&lt;p&gt;If your application claims to maintain a bounded top-&lt;em&gt;N&lt;/em&gt; Binance order book, enforce that boundary.&lt;/p&gt;

&lt;p&gt;A simplified pruning function could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;prune_side&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;side&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;descending&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;ordered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;side&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
        &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;descending&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_quantity&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ordered&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:]:&lt;/span&gt;
        &lt;span class="k"&gt;del&lt;/span&gt; &lt;span class="n"&gt;side&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Applied to both sides:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;prune_side&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;descending&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;prune_side&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;descending&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For bids, higher prices are better.&lt;/p&gt;

&lt;p&gt;For asks, lower prices are better.&lt;/p&gt;

&lt;p&gt;The exact retention strategy depends on your use case, market, performance requirements, and the depth corridor you promise to consumers. The critical part is that the policy must exist.&lt;/p&gt;

&lt;p&gt;An initial snapshot size alone is not a retention policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resync must be normal behavior
&lt;/h2&gt;

&lt;p&gt;Many trading systems treat resynchronization as an exceptional failure.&lt;/p&gt;

&lt;p&gt;It should be treated as part of normal order book lifecycle management.&lt;/p&gt;

&lt;p&gt;A resync may be required after:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a missing update ID,&lt;/li&gt;
&lt;li&gt;an invalid first buffered event,&lt;/li&gt;
&lt;li&gt;a WebSocket reconnect,&lt;/li&gt;
&lt;li&gt;a buffer overflow,&lt;/li&gt;
&lt;li&gt;an internal processing delay,&lt;/li&gt;
&lt;li&gt;an invariant violation,&lt;/li&gt;
&lt;li&gt;a crossed local book,&lt;/li&gt;
&lt;li&gt;or an operator-requested refresh.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A robust state machine should expose states such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INITIALIZING
SYNCHRONIZED
OUT_OF_SYNC
RESYNCING
STOPPED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Consumers should never have to infer those states from stale timestamps or log files.&lt;/p&gt;

&lt;p&gt;Before returning bids or asks, the service should know whether the Binance order book is currently trustworthy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not silently serve stale order book data
&lt;/h2&gt;

&lt;p&gt;Imagine an API consumer requests BTCUSDT asks while the cache is resynchronizing.&lt;/p&gt;

&lt;p&gt;The convenient behavior is to return the last known data with HTTP 200.&lt;/p&gt;

&lt;p&gt;The honest behavior is to return an explicit trust-state error.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#6000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DepthCache 'BTCUSDT' for 'binance.com' is out of sync!"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The consumer can then choose to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;retry,&lt;/li&gt;
&lt;li&gt;temporarily reduce confidence,&lt;/li&gt;
&lt;li&gt;use a redundant replica,&lt;/li&gt;
&lt;li&gt;disable a strategy,&lt;/li&gt;
&lt;li&gt;or stop trading that market.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That decision belongs to the consumer.&lt;/p&gt;

&lt;p&gt;The order book layer's responsibility is to preserve the truth about its own state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validate more than update IDs
&lt;/h2&gt;

&lt;p&gt;Sequence validation is essential, but useful runtime invariants go further.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Invariant&lt;/th&gt;
&lt;th&gt;Possible meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;best_bid &amp;gt;= best_ask&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;crossed or corrupted local order book&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;unexpected update-ID transition&lt;/td&gt;
&lt;td&gt;missing, duplicated, or reordered event&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;event buffer exceeds capacity&lt;/td&gt;
&lt;td&gt;consumer cannot keep up&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;stream silence exceeds threshold&lt;/td&gt;
&lt;td&gt;dead or stalled connection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;maintained depth grows beyond policy&lt;/td&gt;
&lt;td&gt;missing or ineffective pruning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cache age exceeds threshold&lt;/td&gt;
&lt;td&gt;stale data path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;replica states disagree for too long&lt;/td&gt;
&lt;td&gt;one cache may be unhealthy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A crossed book should be handled carefully because fast-moving markets and independently timed observations can complicate comparisons. But inside one consistently applied local state, impossible relationships are valuable warning signals.&lt;/p&gt;

&lt;p&gt;The broader rule is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Make corruption observable before it reaches the strategy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why one correct local order book is still not enough
&lt;/h2&gt;

&lt;p&gt;For one bot and one market, an in-process Binance L2 order book can be a reasonable design.&lt;/p&gt;

&lt;p&gt;Then the system grows.&lt;/p&gt;

&lt;p&gt;You add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;another strategy,&lt;/li&gt;
&lt;li&gt;a dashboard,&lt;/li&gt;
&lt;li&gt;a monitoring service,&lt;/li&gt;
&lt;li&gt;an alerting process,&lt;/li&gt;
&lt;li&gt;a backtesting or research consumer,&lt;/li&gt;
&lt;li&gt;a Node.js application,&lt;/li&gt;
&lt;li&gt;a Go execution service,&lt;/li&gt;
&lt;li&gt;or a script that only needs the top five asks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If each application reconstructs the same Binance order book independently, each one now owns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WebSocket lifecycle handling,&lt;/li&gt;
&lt;li&gt;REST snapshot initialization,&lt;/li&gt;
&lt;li&gt;buffering,&lt;/li&gt;
&lt;li&gt;sequence validation,&lt;/li&gt;
&lt;li&gt;pruning,&lt;/li&gt;
&lt;li&gt;resync,&lt;/li&gt;
&lt;li&gt;memory management,&lt;/li&gt;
&lt;li&gt;and exchange rate-limit consumption.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A reconnect storm duplicates the same initialization work.&lt;/p&gt;

&lt;p&gt;Two processes can temporarily hold different views of the same market.&lt;/p&gt;

&lt;p&gt;Restarting application logic also destroys and rebuilds market-data state that did not need to be coupled to that application.&lt;/p&gt;

&lt;p&gt;At this point, the order book is no longer an implementation detail of the bot.&lt;/p&gt;

&lt;p&gt;It is infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat the Binance order book as a shared service
&lt;/h2&gt;

&lt;p&gt;The architectural shift is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;one synchronized order book layer
many independent consumers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bot A -&amp;gt; Binance -&amp;gt; local BTCUSDT cache
bot B -&amp;gt; Binance -&amp;gt; local BTCUSDT cache
dashboard -&amp;gt; Binance -&amp;gt; local BTCUSDT cache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Binance
   |
shared synchronized depth-cache layer
   |          |          |
 bot A      bot B     dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This provides a single place to implement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;snapshot and WebSocket synchronization,&lt;/li&gt;
&lt;li&gt;sequence validation,&lt;/li&gt;
&lt;li&gt;retention,&lt;/li&gt;
&lt;li&gt;resync,&lt;/li&gt;
&lt;li&gt;cache-state reporting,&lt;/li&gt;
&lt;li&gt;replicas,&lt;/li&gt;
&lt;li&gt;failover,&lt;/li&gt;
&lt;li&gt;monitoring,&lt;/li&gt;
&lt;li&gt;and client access.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The consuming application can remain focused on strategy or analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical implementation with UBDCC
&lt;/h2&gt;

&lt;p&gt;I built the open-source UNICORN Binance DepthCache Cluster, or UBDCC, for this architecture.&lt;/p&gt;

&lt;p&gt;UBDCC runs synchronized Binance DepthCaches as a standalone service and exposes them over HTTP/JSON.&lt;/p&gt;

&lt;p&gt;It is written in Python, but clients do not need to be Python applications.&lt;/p&gt;

&lt;p&gt;Anything that can call an HTTP endpoint can consume the same Binance order book:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python,&lt;/li&gt;
&lt;li&gt;JavaScript,&lt;/li&gt;
&lt;li&gt;Go,&lt;/li&gt;
&lt;li&gt;Rust,&lt;/li&gt;
&lt;li&gt;Java,&lt;/li&gt;
&lt;li&gt;C#,&lt;/li&gt;
&lt;li&gt;PHP,&lt;/li&gt;
&lt;li&gt;Bash,&lt;/li&gt;
&lt;li&gt;dashboards,&lt;/li&gt;
&lt;li&gt;spreadsheets,&lt;/li&gt;
&lt;li&gt;or internal services.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A local test setup starts with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install &lt;/span&gt;ubdcc
ubdcc start &lt;span class="nt"&gt;--dcn&lt;/span&gt; 4
ubdcc-dashboard start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a BTCUSDT DepthCache with two replicas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s1"&gt;'http://127.0.0.1:42081/create_depthcaches'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "exchange": "binance.com",
    "markets": ["BTCUSDT"],
    "desired_quantity": 2
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Query the first five asks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s1"&gt;'http://127.0.0.1:42081/get_asks?exchange=binance.com&amp;amp;market=BTCUSDT&amp;amp;limit_count=5'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Query the first five bids:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s1"&gt;'http://127.0.0.1:42081/get_bids?exchange=binance.com&amp;amp;market=BTCUSDT&amp;amp;limit_count=5'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With two replicas, the same market exists on separate DepthCache nodes. If one node fails, another synchronized replica can serve the request.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9srx52j61aseva2nj1v1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9srx52j61aseva2nj1v1.png" alt=" " width="800" height="727"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;UBDCC is not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a trading strategy,&lt;/li&gt;
&lt;li&gt;an execution engine,&lt;/li&gt;
&lt;li&gt;a backtesting framework,&lt;/li&gt;
&lt;li&gt;or a promise of profitable trading.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is a market-data layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local library, service, or cluster?
&lt;/h2&gt;

&lt;p&gt;Not every project needs distributed infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use a local in-process Binance order book when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;you have one process,&lt;/li&gt;
&lt;li&gt;only one consumer needs the data,&lt;/li&gt;
&lt;li&gt;temporary loss during restart is acceptable,&lt;/li&gt;
&lt;li&gt;and you are prepared to own synchronization and retention logic.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use a standalone shared service when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;several applications need the same markets,&lt;/li&gt;
&lt;li&gt;applications use different programming languages,&lt;/li&gt;
&lt;li&gt;rebuilding caches during every deployment is wasteful,&lt;/li&gt;
&lt;li&gt;or you want one observable trust state.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use replicas or a cluster when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;order book availability matters,&lt;/li&gt;
&lt;li&gt;one process must not be a single point of failure,&lt;/li&gt;
&lt;li&gt;you maintain many markets,&lt;/li&gt;
&lt;li&gt;initialization pressure must be distributed,&lt;/li&gt;
&lt;li&gt;or consumers require explicit failover.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The architecture should follow the reliability contract you actually need.&lt;/p&gt;

&lt;h2&gt;
  
  
  A checklist for a trustworthy Binance L2 order book
&lt;/h2&gt;

&lt;p&gt;Before trusting a local or remote Binance order book, verify that the implementation can answer all of these:&lt;/p&gt;

&lt;h3&gt;
  
  
  Initialization
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Does it subscribe before fetching the snapshot?&lt;/li&gt;
&lt;li&gt;Does it buffer WebSocket events while the snapshot is in flight?&lt;/li&gt;
&lt;li&gt;Does it discard stale events correctly?&lt;/li&gt;
&lt;li&gt;Does it find the correct first applicable update?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Continuity
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Does it validate Spot and Futures sequences using the correct fields?&lt;/li&gt;
&lt;li&gt;Does any continuity failure trigger a full resync?&lt;/li&gt;
&lt;li&gt;Can the event buffer overflow silently?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  State management
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Is synchronization state explicit and queryable?&lt;/li&gt;
&lt;li&gt;Are reads rejected or marked while the cache is out of sync?&lt;/li&gt;
&lt;li&gt;Are reconnect and resync events observable?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Long-running correctness
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Is there an explicit retention boundary?&lt;/li&gt;
&lt;li&gt;Are old levels pruned?&lt;/li&gt;
&lt;li&gt;Does maintained depth remain bounded?&lt;/li&gt;
&lt;li&gt;Is the complete book audited periodically, not only best bid and ask?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Operations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Can the cache recover without restarting the strategy?&lt;/li&gt;
&lt;li&gt;Are metrics and health states exposed?&lt;/li&gt;
&lt;li&gt;Can replicas fail independently?&lt;/li&gt;
&lt;li&gt;Can consumers distinguish fresh data from stale data?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any answer is “I do not know,” that is where to investigate next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Most Binance order book tutorials stop after they have combined one REST snapshot with one WebSocket stream.&lt;/p&gt;

&lt;p&gt;That is the beginning, not the end.&lt;/p&gt;

&lt;p&gt;A production-grade Binance L2 order book needs at least four separate guarantees:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Correct bootstrap&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The snapshot and buffered events form one continuous initial state.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Continuous sequence validation&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Missing or reordered updates cause a loud resync.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Explicit retention&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The cache does not silently become a museum of historical price levels.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Observable trust state&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Consumers know whether the book is synchronized before using it.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And once several applications need the same data, there is a fifth:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Shared, redundant infrastructure&lt;/strong&gt;
Order book reconstruction should not be duplicated inside every bot.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The difficult part is not receiving Binance depth data.&lt;/p&gt;

&lt;p&gt;The difficult part is knowing when that data still deserves to be called an order book.&lt;/p&gt;

&lt;h2&gt;
  
  
  Source code and related research
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/oliver-zehentleitner/unicorn-binance-depth-cache-cluster" rel="noopener noreferrer"&gt;UNICORN Binance DepthCache Cluster&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/oliver-zehentleitner/unicorn-binance-local-depth-cache" rel="noopener noreferrer"&gt;UNICORN Binance Local Depth Cache&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.technopathy.club/your-binance-order-book-is-wrong-here-s-why" rel="noopener noreferrer"&gt;Your Binance Order Book Is Wrong — Here's Why&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.technopathy.club/your-binance-depthcache-is-rotting-here-s-the-proof-in-25-hours" rel="noopener noreferrer"&gt;Your Binance DepthCache is rotting — here's the proof in 25 hours&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.technopathy.club/ubdcc-deep-dive-building-a-trust-layer-for-binance-order-books" rel="noopener noreferrer"&gt;UBDCC Deep-Dive: Building a Trust Layer for Binance Order Books&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.technopathy.club/from-pip-install-to-a-redundant-binance-order-book-cluster-ubdcc-dashboard-quickstart" rel="noopener noreferrer"&gt;UBDCC local quickstart&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Questions, corrections, failed test cases, and contradictory results are welcome in the comments.&lt;/p&gt;




&lt;p&gt;I hope you found this informative and useful.&lt;/p&gt;

&lt;p&gt;Follow me on &lt;a href="https://www.binance.com/en/square/profile/oliver-zehentleitner" rel="noopener noreferrer"&gt;Binance Square&lt;/a&gt;, &lt;a href="https://github.com/oliver-zehentleitner" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, &lt;a href="https://x.com/unicorn_oz" rel="noopener noreferrer"&gt;X&lt;/a&gt;, and &lt;a href="https://www.linkedin.com/in/oliver-zehentleitner/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, or join &lt;a href="https://t.me/unicorndevs" rel="noopener noreferrer"&gt;Telegram&lt;/a&gt; for updates on my latest publications. Constructive feedback is always appreciated.&lt;/p&gt;

&lt;p&gt;Thank you for reading, and happy coding! ¯\_(ツ)_/¯&lt;/p&gt;

</description>
      <category>python</category>
      <category>cryptocurrency</category>
      <category>opensource</category>
      <category>learning</category>
    </item>
  </channel>
</rss>
