Some checks failed
p2p-intel / engine (push) Has been cancelled
p2p-intel / coverage (push) Has been cancelled
p2p-intel / makepad-app (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
doc-engine / engine (push) Has been cancelled
doc-engine / coverage (push) Has been cancelled
doc-engine / consumer (push) Has been cancelled
email / gates (push) Has been cancelled
email / email-domain (push) Has been cancelled
email / nigig-email (push) Has been cancelled
email / supply-chain (push) Has been cancelled
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
nigig-build (CAD) / cad-widget-coverage (push) Has been cancelled
nigig-map / test (push) Has been cancelled
sms / gates (push) Has been cancelled
sms / robius-sms (push) Has been cancelled
sms / android (push) Has been cancelled
sms / nigig-sms (push) Has been cancelled
sms / supply-chain (push) Has been cancelled
spreadsheet / engine-coverage (push) Has been cancelled
spreadsheet / ui-controller-coverage (push) Has been cancelled
A new nested workspace under crates/apps/p2p-intel: six engine crates, a CLI, and a Makepad dashboard that builds for desktop, Android and iOS. It reads public P2P adverts, measures the spread that is actually fillable, alerts when one is worth acting on, and tracks what the float really cost. It never places an order. Binance publishes no P2P trading API, and automating an escrow release is how a merchant loses their float to chargeback fraud. This is the intelligence layer; execution stays manual. The design came from a live capture rather than a sketch, and the capture contradicted the sketch three times. All three are now pinned by tests against checked-in real payloads. **The best price is routinely the least fillable one.** In the KES book the top sell advert was 134.60 from a merchant with three completed trades, implying a 3.6% spread; the next was 130.30. Another advert showed a 0% completion rate. A best-price scan with no quality floor does not find opportunities, it finds outliers, and outliers on a P2P book are bait or a merchant about to run dry. QualityFilter defaults to 95% completion and 50 orders, and analyse() reports every exclusion with its reason rather than dropping it silently. The honest consequence is recorded in the integration suite: at those defaults **not one sell-side advert in the captured KES book qualified**. There was no fillable arbitrage. A tool that reported the raw best-price number would have sent its user after a trade that does not exist, so the test asserts best_sell is None and net_bps is None rather than asserting a comfortable number. **tradeType is inverted between request and response.** Asking the endpoint for tradeType "BUY" returns adverts whose own adv.tradeType reads "SELL". Both are correct: the request parameter is what you want to do, the response field is what the advertiser is doing. Conflating them inverts every spread and the result still looks plausible, which makes it the most expensive mistake available here. Side keeps the two apart with request_trade_type()/advert_trade_type(), and a test asserts they are never equal. **An empty market answers HTTP 200 with success: true.** NGN returned zero adverts. "No ads" and "no answer" need opposite responses, so is_empty_market() is a named predicate and ScanError separates Malformed (Binance changed the payload; retrying makes it worse) from Network (transient). basis_points_above returns None against a zero base rather than an infinity, so an empty book cannot read as an infinite opportunity at 3am. Money is never a float, following the rule in nigig-pay-domain/src/money.rs. IEEE 754 cannot represent 0.1 and a spread is a difference of two nearly equal numbers, which is exactly where binary floating point loses the digits that matter. Binance sends prices as decimal strings, so Price parses them straight into scaled i128 integers and never passes through f64. i128 rather than i64 because the intermediate in a bps calculation overflows, not the result. Excess precision is refused rather than rounded and a thousands separator is refused rather than dropped: "1,299.92" read as 129992 is a 1000x error that still looks like a price. Tests pin 0.1 + 0.2 == 0.3 and rotate 100 round trips at one price asserting exactly zero P&L. Alerting is mostly restraint. At a 30-second poll one wide spread would fire 120 identical messages an hour, and a channel that cries wolf gets muted, at which point the tool has negative value because the user believes they are covered. AlertGate suppresses repeats inside a cooldown and re-alerts early only when the spread improves materially -- a collapsing spread is not worth waking someone for. Telegram MarkdownV2 escaping is tested against a real merchant name from the capture, Twin_traders00, whose underscore would otherwise make Telegram reject the message with a 400 and deliver nothing. Writing the dashboard view model found a bug in my own comparator: sorting descending by swapping the tuple to (b, a) also silently swaps the meaning of the None arms, which put dead markets at the top of the opportunity list. The test that caught it was written first and named for the behaviour, not the implementation. Networking is behind a non-default `live` feature, so an ordinary cargo test cannot make a request and CI never depends on Binance being reachable. A CI step asserts reqwest is absent from the default dependency graph so this cannot regress quietly. A live scan was run once to confirm the fixtures match reality; it reported a negative spread for KES and an empty NGN book, which is the tool working correctly. Conventions follow the repo rather than the generic layout in the request: .forgejo/workflows/p2p-intel.yml rather than .github, and no Dockerfile, since the stack is pure Rust and nothing else here is containerised. error_set is used instead of anyhow, matching nigig-core. The root Cargo.toml excludes the nested workspace by name, as it already does for makepad_table, so the isolation is intentional rather than dependent on a table inside someone else's manifest. 106 tests, coverage 96.86% with per-file floors enforced by tools/test-p2p-coverage.sh. Both the total and per-file gates were verified to actually fail by running them with impossible floors; a gate that cannot fail is decoration. Two files are excluded and only because they were first emptied of decisions: the Makepad widget, which needs a GPU and a windowing backend this repo has no headless backend for, and the CLI main, which is argument parsing and println. Every rule the widget renders lives in view_model.rs, measured at 97%. That split is deliberate -- spreadsheet-ui/grid.rs once hid 36 pure functions behind a file-level exclusion, and excluding a file you have not emptied of logic is how that happens. The Makepad desktop binary was built and linked in the sandbox to prove the app half is real and not just a compiling stub.
5 lines
139 B
Text
5 lines
139 B
Text
# Local secrets and live trade data. workflow.md section 9: no credential in
|
|
# a committed file.
|
|
.env
|
|
data/trades.json
|
|
data/inventory.json
|