Keep track of positions across any exchange, broker, or external system. Detect orphans, prevent leaks. Self-healing state reconciliation for any stateful bot.
Keep track of positions across any exchange, broker, or external system. Detect orphans, prevent leaks. Self-healing state reconciliation for any stateful bot.
Position Tracker is a generalized state machine for tracking positions with self-healing reconciliation against external APIs.
Originally built for high-frequency crypto trading, now generalized for any domain where you need to track external positions that can become orphaned or phantom.
You're tracking positions across an external system (exchange, broker, cloud provider, API service). Your local state says you have 3 active positions. The external API says you have 5.
Which is correct?
Without reconciliation, you get:
Manual fixes don't scale. You need self-healing reconciliation.
Position Tracker implements:
Track positions through their full lifecycle:
WATCHING → IN → EXITED → cleanup
Each state has:
Detect positions that exist externally but not in your local state:
Remove local state entries that don't correspond to external reality:
Ignore sub-threshold positions to prevent noise:
Periodic health checks against external API:
pip install position-tracker
# OR copy position_tracker.py to your project
from position_tracker import PositionTracker, ExternalAPIAdapter
class MyBrokerAdapter(ExternalAPIAdapter):
def get_all_positions(self):
"""Fetch all positions from your broker/exchange."""
# Return list of {"id": str, "value": float, "metadata": dict}
return self.broker_client.get_positions()
def close_position(self, position_id):
"""Close a position on the broker/exchange."""
return self.broker_client.close(position_id)
tracker = PositionTracker(
api_adapter=MyBrokerAdapter(),
min_position_value=5.0, # Ignore positions < $5
max_positions=10, # Safety cap
)
# Track a new position
tracker.track("AAPL-123", metadata={"entry_price": 150.00})
# Update state
tracker.update("AAPL-123", action="ENTER", value=1000.0)
# Check for orphans (self-healing)
orphans = tracker.detect_orphans()
if orphans:
tracker.cleanup_orphans(orphans)
# Reconcile against external API
tracker.reconcile()
Time to integrate: 20-30 minutes.
Track open positions across exchanges. Detect orphaned trades that weren't recorded locally. Prevent position leaks that cost money.
Track active cloud instances (AWS EC2, GCP VMs, etc.). Detect orphaned instances that are still billing but not tracked. Auto-cleanup to save costs.
Track active API sessions, subscriptions, or quota allocations. Detect orphaned sessions that consume quota. Cleanup to free resources.
Track open database connections. Detect orphaned connections that weren't properly closed. Prevent connection leaks.
Track active subscriptions across services. Detect orphans (subscriptions you forgot about). Cancel to save money.
Any domain where external state can diverge from local tracking benefits from this.
WATCHING: Position detected, not yet entered
↓ (action=ENTER)
IN: Active position being tracked
↓ (action=EXIT)
EXITED: Position closed, pending cleanup
↓ (timeout)
(deleted) Cleaned up after timeout
1. Fetch all positions from external API
2. Compare against local state
3. Flag positions in API but not in state (orphans)
4. Filter by min_position_value (ignore dust)
5. Log orphans for review
6. (Optional) Auto-cleanup via API adapter
1. Periodic poll (e.g., every 5 minutes)
2. Get local IN positions
3. Verify each exists in external API
4. Flag phantoms (local but not external)
5. Flag orphans (external but not local)
6. Auto-correct or alert based on config
tracker = PositionTracker(
api_adapter=adapter,
state_dir="./state", # Where to persist state
min_position_value=5.0, # Dust threshold ($5)
max_positions=10, # Safety cap
skip_cleanup_seconds=3600, # Clean up SKIPPED after 1h
exited_cleanup_seconds=1800, # Clean up EXITED after 30m
)
tracker = PositionTracker(
# ... basic config ...
enable_auto_reconcile=True, # Auto-fix orphans/phantoms
reconcile_interval=300, # Reconcile every 5 min
log_file="./logs/tracker.log", # Logging
alert_callback=my_alert_func, # Custom alerting
)
See config_example.json for full options.
track(position_id, metadata=None)Start tracking a new position.
Args:
position_id (str): Unique identifiermetadata (dict, optional): Additional data to storeReturns: PositionState object
update(position_id, action, value=0.0)Update position state based on action.
Args:
position_id (str): Position to updateaction (str): One of ENTER, EXIT, HOLD, MONITOR, SKIPvalue (float): Current position valueReturns: Updated PositionState object
detect_orphans()Detect positions on external API that aren't in local state.
Returns: List of orphan position dicts [{"id": str, "value": float, ...}]
cleanup_orphans(orphans, auto_close=False)Handle orphaned positions.
Args:
orphans (list): List from detect_orphans()auto_close (bool): If True, close orphans via API adapterReturns: List of cleaned up position IDs
reconcile()Reconcile local state against external API.
Returns: Reconciliation report dict with orphans, phantoms, corrected
ENTER — Enter a position (WATCHING → IN)EXIT — Exit a position (IN → EXITED)HOLD — Hold current position (stay in IN)MONITOR — Continue monitoring (stay in WATCHING)SKIP — Skip this position (WATCHING → SKIPPED)try:
orphans = tracker.detect_orphans()
except ExternalAPIError as e:
logger.error(f"API unreachable: {e}")
# Fall back to local state only
report = tracker.reconcile()
if report["errors"]:
logger.warning(f"Reconciliation errors: {report['errors']}")
# Manual intervention needed
state = tracker.get("unknown-id")
if state is None:
logger.info("Position not tracked")
pytest tests/test_position_tracker.py
from position_tracker import PositionTracker, MockAPIAdapter
adapter = MockAPIAdapter()
adapter.add_position("test-1", value=100.0)
tracker = PositionTracker(adapter)
tracker.track("test-1")
tracker.update("test-1", "ENTER", value=100.0)
# Simulate orphan
adapter.add_position("test-2", value=200.0)
orphans = tracker.detect_orphans()
assert len(orphans) == 1
assert orphans[0]["id"] == "test-2"
See LIMITATIONS.md for details, including:
Cause: Dust threshold too high, filtering out real positions
Fix: Lower min_position_value to match your use case
Cause: Old EXITED positions not cleaning up
Fix: Check exited_cleanup_seconds config, ensure timeouts are working
Cause: External API unreachable or credentials invalid
Fix: Implement retry logic in your adapter, add error handling
state_file=your_export.jsonreconcile() to validatePosition Tracker is open source and community-maintained.
Contribution guidelines: Open an issue or PR on GitHub. We review everything.
Position Tracker solves a universal infrastructure problem — tracking external state reliably. Hoarding the solution helps no one.
This is a goodwill project to:
Position Tracker is free, but if it saved you time:
☕ Ko-fi: https://ko-fi.com/theshadowrose
Donations support ongoing testing, docs, and new features.
MIT License — use freely, credit appreciated.
Copyright © 2026 Shadow Rose
See LICENSE for details.
Want to work on these? Open an issue or reach out on Discord.
Created by: Shadow Rose
Extracted from: Production trading bot (proven in live trading)
Philosophy: Vibe coding — infrastructure that heals itself
License: MIT
Built on lessons learned from a real orphan catastrophe — dozens of positions leaked, manual cleanup required. Never again.
External state will diverge from local state. That's not a bug, it's reality.
The question is: does your system detect and heal the divergence automatically, or do you find out when the bill arrives?
Position Tracker ensures you find out before the bill arrives.
Ship it. Break it. Tell us what you find.
— Shadow Rose
February 2026
☕ Support: https://ko-fi.com/theshadowrose
🐦 Follow: https://x.com/TheShadowyRose
💬 Community: https://discord.com/invite/clawd
This software is provided "AS IS", without warranty of any kind, express or implied.
USE AT YOUR OWN RISK.
By downloading, installing, or using this software, you acknowledge that you have read this disclaimer and agree to use the software entirely at your own risk.
| 🐛 Bug Reports | TheShadowyRose@proton.me |
| ☕ Ko-fi | ko-fi.com/theshadowrose |
| 🛒 Gumroad | shadowyrose.gumroad.com |
| @TheShadowyRose | |
| 🐙 GitHub | github.com/TheShadowRose |
| 🧠 PromptBase | promptbase.com/profile/shadowrose |
Built with OpenClaw — thank you for making this possible.
🛠️ Need something custom? Custom OpenClaw agents & skills starting at $500. If you can describe it, I can build it. → Hire me on Fiverr
ZIP package — ready to use