Heuristic:HKUDS AI Trader Position File Locking
| Knowledge Sources | |
|---|---|
| Domains | Concurrency, Trading |
| Last Updated | 2026-02-09 14:00 GMT |
Overview
POSIX file-based locking (fcntl.flock) to serialize concurrent position file updates, preventing race conditions when multiple agent instances trade the same portfolio simultaneously.
Description
When running multiple agents in parallel (via main_parrallel.py), each agent operates on its own position file (position.jsonl). However, within a single agent's trading session, buy and sell operations must be atomic: reading the current position, calculating the new position, and writing the update must happen without interleaving. The _position_lock() context manager uses POSIX fcntl.flock(LOCK_EX) to acquire an exclusive file lock per signature (model name), preventing data corruption from concurrent MCP tool calls.
Usage
This heuristic is automatically applied in all buy() and sell() MCP tool functions. When extending the trading tools or adding new position-modifying operations, wrap the critical section with _position_lock(signature). Note: this only works on POSIX systems (Linux, macOS).
The Insight (Rule of Thumb)
- Action: Wrap all position read-modify-write operations in
with _position_lock(signature):. - Value: Exclusive lock per agent signature via
.position.lockfile. - Trade-off: Serializes all trades for a given agent, reducing parallelism within one agent. This is acceptable since trading decisions are sequential per agent.
Reasoning
JSONL files are append-only by design, but reading the latest position and then appending an updated position is a two-step operation vulnerable to TOCTOU (time-of-check-time-of-use) race conditions. Without locking, two concurrent buy/sell calls could read the same "latest position" and both write updates based on stale data. File-based locking was chosen over database locks because it requires no additional infrastructure and aligns with the file-based data storage pattern used throughout the project.
Code Evidence
Position lock implementation from agent_tools/tool_trade.py:23-52:
def _position_lock(signature: str):
"""Context manager for file-based lock to serialize position updates per signature."""
class _Lock:
def __init__(self, name: str):
log_path = get_config_value("LOG_PATH", "./data/agent_data")
if os.path.isabs(log_path):
base_dir = Path(log_path) / name
else:
if log_path.startswith("./data/"):
log_rel = log_path[7:] # strip "./data/"
else:
log_rel = log_path
base_dir = Path(project_root) / "data" / log_rel / name
base_dir.mkdir(parents=True, exist_ok=True)
self.lock_path = base_dir / ".position.lock"
self._fh = open(self.lock_path, "a+")
def __enter__(self):
fcntl.flock(self._fh.fileno(), fcntl.LOCK_EX)
return self
def __exit__(self, exc_type, exc, tb):
try:
fcntl.flock(self._fh.fileno(), fcntl.LOCK_UN)
finally:
self._fh.close()
return _Lock(signature)
Lock usage in buy operation from agent_tools/tool_trade.py:134:
with _position_lock(signature):
try:
current_position, current_action_id = get_latest_position(today_date, signature)
except Exception as e:
# ... error handling