Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions docs/fiqh-basis.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,49 @@ The fiqh content — that derivatives and difference-settlement are impermissibl
(§65.6: what makes speculation *maisir* is non-ownership, non-delivery, difference-settlement)
but the RAILS are the charter enforcing it.

### Long-only — *bay' ma la yamlik*, §65.4/§65.6/§65.11

keel never sells what it does not hold, and four independent places enforce it:

- **The rule contract.** `Setup` declares `direction: Literal["long"]`, and `__post_init__`
enforces at runtime the one thing that annotation only asserts statically — so a foreign
rule cannot propose a short, and cannot propose one wearing a long's label (#447).
- **The engine.** `keel/strategy/engine.py` builds every entry `Signal` with `side=Side.BUY`,
unconditionally. No branch produces a `Side.SELL` entry.
- **Rail 10.** Every SELL must cite a defined rule — no arbitrary liquidation.
- **Rails 18/19.** The spot-only charter: nothing that settles by price difference.

The doctrine, from the same source the rest of this document rests on. §65.11 (Ayub Ch 5.4.2):
*"short-selling has been prohibited by almost all scholars"* — because the subject matter
*"must be existing/existable… capable of ownership/title, capable of delivery/possession… and
the seller must have its title and risk."* §65.6 draws the same boundary from the other side:
what converts speculation into *maisir* is **(a) not owning it, (b) not possessing it, (c) not
taking or making delivery, or (d) settling by price difference** — never frequency, never
volatility.

**Unlike rails 18/19, this one is a fiqh-derivation and not a charter.** It descends from the
prohibition Ayub states at Ch 6.5.1 (§65.4) as *"do not sell what you do not possess"* —
*bay' ma la yamlik*. That anchor is more direct than riba for this class of failure, and the
difference matters: a sale of what the seller does not hold is impermissible with no interest
anywhere in the transaction, so a riba-framed defence does not reach it.

⚠️ One qualification, because §65.4 is invoked here for nearly the opposite of what it was read
for. §65.4's own point is that the prohibition is **not** a physical-custody rule — *"what is
meant by possession here is the inability to deliver the goods"* — which is exactly how
exchange-held balances qualify as valid `qabd`. That lenient reading does not weaken the
long-only claim; it sharpens it. On the constructive-possession test the question is whether
anything prevents delivery, and in the failure modes recorded under "Known open questions" the
asset is not there at all. Selling it fails the prohibition on the lenient reading, not merely
the strict one.

**On the primary text.** The doctrine is conventionally anchored to the hadith of Hakim ibn
Hizam — *"Do not sell what is not with you"* (لا تبع ما ليس عندك), Sunan an-Nasa'i 4613 /
Sunan Abu Dawud 3503. **That reference is not extracted in this repository's knowledge base**,
and is recorded here as the operator's citation: checkable against the collections, but
carrying no `§N.x` row and not treated as one. The in-repo authority for everything above is
§65.4/§65.6/§65.11, corroborated by §28.2's ownership principle and §33's gharar reading of
*"the sale of items not owned"*.

### Purification (§65.9) and idle-balance rewards (§56.3)

`keel/compliance/purification.py` implements Ayub §65.9: interest/reward credits are
Expand Down Expand Up @@ -224,6 +267,18 @@ Stated, not hidden — each is a place where keel's encoded behaviour could be w
Whether "no underlying purpose" is disqualifying "is exactly the kind of judgement the
screen defers to a human" (`docs/experiments/2026-07-20-candidate-universe.md`) — deferred,
not decided.
- **Long-only is enforced where keel DECIDES, not where it SETTLES.** Everything under
"Long-only" above governs what keel chooses. It does not govern the quantity that reaches
the venue. A SELL is never clamped to the account's available base (#667): the exit sells
what the ledger believes is held, and the ledger drifts from the account through fees taken
in the base asset, partial fills (`filled_quantity` is recorded nowhere on this
deployment), and out-of-band operator transfers. A resting stop leg can outlive the
position that justified it, because no sweep cancels a SELL whose position is gone (#668).
On a cash account each of these is a rejected order — bad, but not a fiqh failure. On a
margin-enabled account the venue fills the difference as a short, and keel has no
cash-account posture check on Coinbase (#666; the check exists only on the Alpaca adapter).
So the long-only ruling above is sound at the decision layer and **not yet closed at the
venue boundary**. Named here rather than left to be discovered.
- **ZEC and the rest of the deferrals.** The candidate-universe record lists the open
questions the attestation step has to answer and "which this agent must not answer".

Expand Down
106 changes: 106 additions & 0 deletions tests/test_fiqh_basis.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,3 +376,109 @@ def test_the_readme_links_the_document():
assert _DOC in (_ROOT / "README.md").read_text(), (
f"README.md must link {_DOC} from its documentation map"
)


_LONG_ONLY_PROHIBITION = "short-selling has been prohibited by"

_LONG_ONLY_SUBJECT_MATTER = "capable of ownership/title, capable of delivery/possession"

_LONG_ONLY_TYPE = 'direction: Literal["long"]'

_LONG_ONLY_SIDE = "side=Side.BUY"

_HADITH_NOT_IN_KB = "That reference is not extracted in this repository's knowledge base"

_SELL_SIZE_CONTRACT = "it can only ever make an exit MORE likely to be accepted"


def test_the_long_only_ruling_is_pinned_two_sided_to_the_code_that_enforces_it():
"""The doc names two enforcement points by their exact source text; both must still exist.

Long-only is the one ruling in this document that is neither an attestation nor a rail:
it is structural, and it holds only because the rule contract cannot express a short and
the engine cannot construct one. Either half silently changing would leave the doc making
a fiqh claim about behaviour the code no longer has.
"""
doc = _unwrapped(_doc())
assert _LONG_ONLY_TYPE in doc and _LONG_ONLY_SIDE in doc, (
f"{_DOC} must name both long-only enforcement points exactly "
f"({_LONG_ONLY_TYPE!r} and {_LONG_ONLY_SIDE!r}) -- a claim that cannot be checked "
"against a line of code is the kind this document exists to refuse"
)
base = (_ROOT / "keel/strategy/rules/base.py").read_text()
assert re.search(r'(?m)^\s+direction: Literal\["long"\]\s*$', base), (
"keel/strategy/rules/base.py must still DECLARE the field as `Literal[\"long\"]`. "
"Matched on the field line, not anywhere in the file: the `__post_init__` docstring "
f"quotes the annotation too, and a prose mention is not a contract {_DOC} can cite"
)
assert 'if self.direction != "long":' in _rel("keel/strategy/rules/base.py"), (
"keel/strategy/rules/base.py must still enforce the direction at RUNTIME -- the "
"annotation is a promise to mypy, and a foreign rule never runs mypy (#447)"
)
assert _LONG_ONLY_SIDE in _rel("keel/strategy/engine.py"), (
"keel/strategy/engine.py must still build every entry Signal as a BUY; if an entry "
f"can be a SELL, {_DOC}'s long-only section is false"
)


def test_the_long_only_doctrine_is_quoted_from_the_knowledge_base():
"""`bay' ma la yamlik` is cited, not asserted -- and the source must still say it.

The document's whole method is that a ruling is checkable against an in-repo extract.
Long-only rests on §65.11's short-selling passage, so the doc's quotation and source-65's
text are pinned to each other in both directions.
"""
doc = _unwrapped(_doc())
source = "docs/superpowers/references/trading-knowledge-base/sources/source-65.md"
text = _rel(source)
for quoted in (_LONG_ONLY_PROHIBITION, _LONG_ONLY_SUBJECT_MATTER):
assert quoted in doc, (
f"{_DOC} must quote §65.11 verbatim ({quoted!r}) -- long-only is a fiqh "
"derivation here, not a charter, so it must carry its citation"
)
assert quoted in text, (
f"{source} must still carry the passage the doc quotes ({quoted!r})"
)


def test_the_hadith_reference_is_marked_as_outside_the_knowledge_base():
"""A primary-text citation with no `§N.x` row must SAY it has none.

Every other citation in this document resolves to an in-repo extract; the hadith of Hakim
ibn Hizam does not. Recording it without the disclaimer would let a reader assume the same
provenance the `§N.x` rows carry -- exactly the papering-over the KB's honesty rules
forbid. Two-sided: the disclaimer must be there, and it must still be TRUE.
"""
assert _HADITH_NOT_IN_KB in _unwrapped(_doc()), (
f"{_DOC} must mark the hadith reference as outside the knowledge base "
f"({_HADITH_NOT_IN_KB!r}) -- an uncited primary text may be recorded, never disguised"
)
sources = _ROOT / "docs/superpowers/references/trading-knowledge-base/sources"
carrying = [p.name for p in sorted(sources.glob("*.md")) if "an-Nasa'i" in p.read_text()]
assert not carrying, (
f"{carrying} now extracts the hadith the doc says is absent -- the disclaimer has "
"become false; give the citation its §N.x row and drop the caveat"
)


def test_the_venue_boundary_gap_is_stated_not_hidden():
"""Long-only holds where keel decides; the doc must admit it is open where keel settles.

A SELL is never clamped to the account's available base, so fee dust, an unrecorded
partial fill, or an out-of-band transfer can send an order for more than is held (#667).
That is `bay' ma la yamlik` reached by arithmetic. Pinned to `_sell_base_size`'s own
contract: while that function's only job is to make an exit more likely to be accepted, it
reads no balance, and the open question must stay in the doc. When #667 lands this test
fails -- which is the point: the doc gets updated with the code, not after it.
"""
doc = _unwrapped(_doc())
assert "not yet closed at the venue boundary" in doc, (
f"{_DOC} must state that long-only is unclosed at the venue boundary -- the ruling is "
"sound at the decision layer and the operational gap is not the reader's to discover"
)
for issue in ("#666", "#667", "#668"):
assert issue in doc, f"{_DOC} must name {issue} as the open venue-boundary work"
assert _SELL_SIZE_CONTRACT in _rel("keel/execution/executor.py"), (
"keel/execution/executor.py's `_sell_base_size` must still carry its stated contract; "
f"if the exit now clamps to a real balance, {_DOC}'s open question is stale"
)