mirror of
https://github.com/saymrwulf/BraiinsRatchet.git
synced 2026-05-14 20:37:52 +00:00
Separate research canaries from profit bids
This commit is contained in:
parent
d9b36082ab
commit
6096434ef4
13 changed files with 120 additions and 29 deletions
|
|
@ -19,6 +19,12 @@ Maximize expected BTC profit, or minimize BTC loss, for manually buying BTC hash
|
|||
4. If the recommendation is executed manually, record the exact order parameters and later realized rewards.
|
||||
5. Keep changes to `strategy.py` only when they improve the measured score under comparable risk.
|
||||
|
||||
Recommendations have different meanings:
|
||||
|
||||
- `observe`: no action.
|
||||
- `manual_canary`: bounded information-buying; expected loss is allowed if it is inside the canary budget.
|
||||
- `manual_bid`: profit-seeking manual action; stricter discount and score guardrails apply.
|
||||
|
||||
## Hard Guardrails
|
||||
|
||||
- No code path places, modifies, or cancels Braiins orders.
|
||||
|
|
@ -41,4 +47,3 @@ score = expected_net_btc - risk_penalty_btc - execution_penalty_btc
|
|||
```
|
||||
|
||||
The strategy must show break-even price, discount to break-even, spend, duration, and maturity assumptions.
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ The first implementation is deliberately conservative:
|
|||
|
||||
- The code never places, modifies, or cancels Braiins orders.
|
||||
- The default strategy emits recommendations only.
|
||||
- The strategy distinguishes `manual_canary` research experiments from `manual_bid` profit-seeking opportunities.
|
||||
- The Braiins integration accepts a watcher-only token only.
|
||||
- All mutable runtime state stays inside this repository under `data/`.
|
||||
- The Git branch is `master`.
|
||||
|
|
@ -54,6 +55,12 @@ The collector first uses unauthenticated public web endpoints from `hashpower.br
|
|||
|
||||
Watcher-only tokens are only relevant if we later need account-specific read-only data such as your private balance, historical fills, or order status. Owner tokens remain out of scope.
|
||||
|
||||
## Recommendation States
|
||||
|
||||
- `observe`: do nothing.
|
||||
- `manual_canary`: a tiny manually executed research experiment is within the configured loss budget.
|
||||
- `manual_bid`: a manually executed bid clears profit-seeking discount and risk guardrails.
|
||||
|
||||
## Documentation
|
||||
|
||||
- `PROGRAM.md`: research charter and ratchet rules.
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ dashboard_url = "https://ocean.xyz/dashboard"
|
|||
max_manual_order_btc = "0.00025"
|
||||
max_daily_spend_btc = "0.00050"
|
||||
max_price_btc_per_eh_day = "0.42"
|
||||
max_canary_price_btc_per_eh_day = "0.52"
|
||||
max_canary_expected_loss_btc = "0.000025"
|
||||
min_discount_to_breakeven = "0.08"
|
||||
min_duration_minutes = 30
|
||||
max_duration_minutes = 720
|
||||
|
|
@ -20,4 +22,3 @@ recommend_only = true
|
|||
target_duration_minutes = 180
|
||||
target_spend_btc = "0.00010"
|
||||
risk_lambda = "0.35"
|
||||
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ Evaluates the latest stored OCEAN and Braiins snapshots against `config.example.
|
|||
PYTHONPATH=src ./.venv/bin/python -m braiins_ratchet.cli evaluate
|
||||
```
|
||||
|
||||
The command returns `observe` or `manual_bid`. It never places an order.
|
||||
The command returns `observe`, `manual_canary`, or `manual_bid`. It never places an order.
|
||||
|
||||
## `report`
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,15 @@ If it says:
|
|||
Strategy action: manual_bid
|
||||
```
|
||||
|
||||
The strategy thinks a small manual canary bid clears the configured guardrails. You still decide manually in the Braiins UI.
|
||||
The strategy thinks a small manual profit-seeking bid clears the configured discount guardrails. You still decide manually in the Braiins UI.
|
||||
|
||||
If it says:
|
||||
|
||||
```text
|
||||
Strategy action: manual_canary
|
||||
```
|
||||
|
||||
The market is not a money-printer setup, but the expected loss is inside the configured research budget. This is the scientific mode: a tiny canary may be useful to learn about execution, timing, OCEAN accounting, stale/reject behavior, and TIDES maturity.
|
||||
|
||||
Important fields:
|
||||
|
||||
|
|
@ -95,7 +103,7 @@ Stop early with `Ctrl-C`.
|
|||
|
||||
## When To Act
|
||||
|
||||
Only consider manual action when all of these are true:
|
||||
Only consider a profit-seeking manual bid when all of these are true:
|
||||
|
||||
- `Strategy action: manual_bid`.
|
||||
- `score_btc` is positive.
|
||||
|
|
@ -105,6 +113,14 @@ Only consider manual action when all of these are true:
|
|||
|
||||
Do not act because OCEAN is "due". Block discovery is memoryless.
|
||||
|
||||
Only consider a research canary when all of these are true:
|
||||
|
||||
- `Strategy action: manual_canary`.
|
||||
- `proposed_spend_btc` is acceptable to lose.
|
||||
- `expected_net_btc` is not worse than the configured canary loss budget.
|
||||
- You are explicitly buying information, not pretending the edge is proven.
|
||||
- You can wait through the maturity window before judging the result.
|
||||
|
||||
## How Long To Wait After A Manual Canary
|
||||
|
||||
Use the report's `maturity` line.
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ Run these commands from the repository root:
|
|||
./scripts/ratchet once
|
||||
```
|
||||
|
||||
The result is a recommendation only. `manual_bid` means the strategy thinks a manually placed bid clears the configured guardrails. `observe` means no action is recommended.
|
||||
The result is a recommendation only. `observe` means no action is recommended. `manual_canary` means a tiny research experiment is inside the configured loss budget. `manual_bid` means a manually placed bid clears profit-seeking discount and risk guardrails.
|
||||
|
||||
The report's sampled price min/avg/max uses public Braiins snapshots only. Manual imports are still stored and can drive evaluation, but they do not pollute live market summary statistics.
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ class GuardrailsConfig:
|
|||
max_manual_order_btc: Decimal
|
||||
max_daily_spend_btc: Decimal
|
||||
max_price_btc_per_eh_day: Decimal
|
||||
max_canary_price_btc_per_eh_day: Decimal
|
||||
max_canary_expected_loss_btc: Decimal
|
||||
min_discount_to_breakeven: Decimal
|
||||
min_duration_minutes: int
|
||||
max_duration_minutes: int
|
||||
|
|
@ -81,6 +83,12 @@ def load_config(path: Path | None = None) -> AppConfig:
|
|||
max_manual_order_btc=_decimal(guardrails.get("max_manual_order_btc"), "0.0001"),
|
||||
max_daily_spend_btc=_decimal(guardrails.get("max_daily_spend_btc"), "0.0002"),
|
||||
max_price_btc_per_eh_day=_decimal(guardrails.get("max_price_btc_per_eh_day"), "0"),
|
||||
max_canary_price_btc_per_eh_day=_decimal(
|
||||
guardrails.get("max_canary_price_btc_per_eh_day"), "0"
|
||||
),
|
||||
max_canary_expected_loss_btc=_decimal(
|
||||
guardrails.get("max_canary_expected_loss_btc"), "0"
|
||||
),
|
||||
min_discount_to_breakeven=_decimal(guardrails.get("min_discount_to_breakeven"), "0.05"),
|
||||
min_duration_minutes=int(guardrails.get("min_duration_minutes", 30)),
|
||||
max_duration_minutes=int(guardrails.get("max_duration_minutes", 720)),
|
||||
|
|
|
|||
|
|
@ -11,16 +11,8 @@ def validate_order(
|
|||
guardrails: GuardrailsConfig,
|
||||
breakeven_btc_per_eh_day: Decimal | None,
|
||||
) -> list[str]:
|
||||
violations: list[str] = []
|
||||
violations = validate_order_structure(order, guardrails)
|
||||
|
||||
if not guardrails.recommend_only:
|
||||
violations.append("recommend_only must remain true in the PoC")
|
||||
if order.spend_btc <= 0:
|
||||
violations.append("spend must be positive")
|
||||
if order.spend_btc > guardrails.max_manual_order_btc:
|
||||
violations.append(f"spend exceeds max_manual_order_btc={guardrails.max_manual_order_btc}")
|
||||
if order.price_btc_per_eh_day <= 0:
|
||||
violations.append("price must be positive")
|
||||
if (
|
||||
guardrails.max_price_btc_per_eh_day > 0
|
||||
and order.price_btc_per_eh_day > guardrails.max_price_btc_per_eh_day
|
||||
|
|
@ -28,10 +20,6 @@ def validate_order(
|
|||
violations.append(
|
||||
f"price exceeds max_price_btc_per_eh_day={guardrails.max_price_btc_per_eh_day}"
|
||||
)
|
||||
if order.duration_minutes < guardrails.min_duration_minutes:
|
||||
violations.append(f"duration below min_duration_minutes={guardrails.min_duration_minutes}")
|
||||
if order.duration_minutes > guardrails.max_duration_minutes:
|
||||
violations.append(f"duration exceeds max_duration_minutes={guardrails.max_duration_minutes}")
|
||||
if breakeven_btc_per_eh_day and breakeven_btc_per_eh_day > 0:
|
||||
required = breakeven_btc_per_eh_day * (Decimal("1") - guardrails.min_discount_to_breakeven)
|
||||
if order.price_btc_per_eh_day > required:
|
||||
|
|
@ -45,6 +33,25 @@ def validate_order(
|
|||
return violations
|
||||
|
||||
|
||||
def validate_order_structure(order: CandidateOrder, guardrails: GuardrailsConfig) -> list[str]:
|
||||
violations: list[str] = []
|
||||
|
||||
if not guardrails.recommend_only:
|
||||
violations.append("recommend_only must remain true in the PoC")
|
||||
if order.spend_btc <= 0:
|
||||
violations.append("spend must be positive")
|
||||
if order.spend_btc > guardrails.max_manual_order_btc:
|
||||
violations.append(f"spend exceeds max_manual_order_btc={guardrails.max_manual_order_btc}")
|
||||
if order.price_btc_per_eh_day <= 0:
|
||||
violations.append("price must be positive")
|
||||
if order.duration_minutes < guardrails.min_duration_minutes:
|
||||
violations.append(f"duration below min_duration_minutes={guardrails.min_duration_minutes}")
|
||||
if order.duration_minutes > guardrails.max_duration_minutes:
|
||||
violations.append(f"duration exceeds max_duration_minutes={guardrails.max_duration_minutes}")
|
||||
|
||||
return violations
|
||||
|
||||
|
||||
def token_looks_unsafe(token: str) -> bool:
|
||||
lowered = token.lower()
|
||||
unsafe_markers = ("owner", "admin", "trade", "write", "order", "secret")
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ class CandidateOrder:
|
|||
|
||||
@dataclass(frozen=True)
|
||||
class StrategyProposal:
|
||||
action: Literal["observe", "manual_bid"]
|
||||
action: Literal["observe", "manual_canary", "manual_bid"]
|
||||
reason: str
|
||||
order: CandidateOrder | None
|
||||
breakeven_btc_per_eh_day: Decimal | None
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from decimal import Decimal
|
|||
|
||||
from .config import AppConfig
|
||||
from .ev import breakeven_btc_per_eh_day, downside_penalty, expected_reward_for_order
|
||||
from .guardrails import validate_order
|
||||
from .guardrails import validate_order, validate_order_structure
|
||||
from .models import CandidateOrder, MarketSnapshot, OceanSnapshot, StrategyProposal
|
||||
|
||||
|
||||
|
|
@ -53,10 +53,10 @@ def propose(
|
|||
score = expected_net - downside_penalty(expected_reward, config.strategy.risk_lambda)
|
||||
|
||||
violations = validate_order(order, config.guardrails, breakeven)
|
||||
if violations:
|
||||
if not violations and score > 0:
|
||||
return StrategyProposal(
|
||||
action="observe",
|
||||
reason="guardrail blocked: " + "; ".join(violations),
|
||||
action="manual_bid",
|
||||
reason="profit-seeking bid clears discount guardrails and positive risk-adjusted score",
|
||||
order=order,
|
||||
breakeven_btc_per_eh_day=breakeven,
|
||||
expected_reward_btc=expected_reward,
|
||||
|
|
@ -65,10 +65,15 @@ def propose(
|
|||
maturity_note=_maturity_note(ocean),
|
||||
)
|
||||
|
||||
if score <= 0:
|
||||
canary_violations = _validate_canary(order, config, expected_net)
|
||||
if not canary_violations:
|
||||
return StrategyProposal(
|
||||
action="observe",
|
||||
reason=f"risk-adjusted score is not positive ({score})",
|
||||
action="manual_canary",
|
||||
reason=(
|
||||
"bounded research canary: profit guardrails not cleared, "
|
||||
f"but expected_net={expected_net} is within loss budget "
|
||||
f"{config.guardrails.max_canary_expected_loss_btc}"
|
||||
),
|
||||
order=order,
|
||||
breakeven_btc_per_eh_day=breakeven,
|
||||
expected_reward_btc=expected_reward,
|
||||
|
|
@ -78,8 +83,12 @@ def propose(
|
|||
)
|
||||
|
||||
return StrategyProposal(
|
||||
action="manual_bid",
|
||||
reason="market price clears configured guardrails and positive risk-adjusted score",
|
||||
action="observe",
|
||||
reason=(
|
||||
"no experiment recommended: "
|
||||
+ "; ".join(violations + canary_violations)
|
||||
+ f"; risk_adjusted_score={score}"
|
||||
),
|
||||
order=order,
|
||||
breakeven_btc_per_eh_day=breakeven,
|
||||
expected_reward_btc=expected_reward,
|
||||
|
|
@ -89,6 +98,24 @@ def propose(
|
|||
)
|
||||
|
||||
|
||||
def _validate_canary(order: CandidateOrder, config: AppConfig, expected_net: Decimal) -> list[str]:
|
||||
violations = validate_order_structure(order, config.guardrails)
|
||||
if (
|
||||
config.guardrails.max_canary_price_btc_per_eh_day > 0
|
||||
and order.price_btc_per_eh_day > config.guardrails.max_canary_price_btc_per_eh_day
|
||||
):
|
||||
violations.append(
|
||||
"price exceeds max_canary_price_btc_per_eh_day="
|
||||
f"{config.guardrails.max_canary_price_btc_per_eh_day}"
|
||||
)
|
||||
if expected_net < -config.guardrails.max_canary_expected_loss_btc:
|
||||
violations.append(
|
||||
f"expected loss {abs(expected_net)} exceeds canary budget "
|
||||
f"{config.guardrails.max_canary_expected_loss_btc}"
|
||||
)
|
||||
return violations
|
||||
|
||||
|
||||
def _observe(reason: str) -> StrategyProposal:
|
||||
return StrategyProposal(
|
||||
action="observe",
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ class GuardrailTests(unittest.TestCase):
|
|||
max_manual_order_btc=Decimal("0.00025"),
|
||||
max_daily_spend_btc=Decimal("0.00050"),
|
||||
max_price_btc_per_eh_day=Decimal("0.42"),
|
||||
max_canary_price_btc_per_eh_day=Decimal("0.52"),
|
||||
max_canary_expected_loss_btc=Decimal("0.000025"),
|
||||
min_discount_to_breakeven=Decimal("0.08"),
|
||||
min_duration_minutes=30,
|
||||
max_duration_minutes=720,
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@ def _config() -> AppConfig:
|
|||
max_manual_order_btc=Decimal("0.00025"),
|
||||
max_daily_spend_btc=Decimal("0.00050"),
|
||||
max_price_btc_per_eh_day=Decimal("0.42"),
|
||||
max_canary_price_btc_per_eh_day=Decimal("0.52"),
|
||||
max_canary_expected_loss_btc=Decimal("0.000025"),
|
||||
min_discount_to_breakeven=Decimal("0.08"),
|
||||
min_duration_minutes=30,
|
||||
max_duration_minutes=720,
|
||||
|
|
|
|||
|
|
@ -31,6 +31,20 @@ class StrategyTests(unittest.TestCase):
|
|||
proposal = propose(config, ocean, market)
|
||||
self.assertEqual(proposal.action, "manual_bid")
|
||||
|
||||
def test_strategy_can_emit_manual_canary_near_breakeven(self) -> None:
|
||||
config = _config()
|
||||
ocean = OceanSnapshot(
|
||||
timestamp_utc="2026-04-25T00:00:00+00:00",
|
||||
network_difficulty_t=Decimal("135.59"),
|
||||
avg_block_time_hours=Decimal("9"),
|
||||
)
|
||||
market = MarketSnapshot(
|
||||
timestamp_utc="2026-04-25T00:00:00+00:00",
|
||||
best_price_btc_per_eh_day=Decimal("0.46"),
|
||||
)
|
||||
proposal = propose(config, ocean, market)
|
||||
self.assertEqual(proposal.action, "manual_canary")
|
||||
|
||||
|
||||
def _config() -> AppConfig:
|
||||
return AppConfig(
|
||||
|
|
@ -45,6 +59,8 @@ def _config() -> AppConfig:
|
|||
max_manual_order_btc=Decimal("0.00025"),
|
||||
max_daily_spend_btc=Decimal("0.00050"),
|
||||
max_price_btc_per_eh_day=Decimal("0.42"),
|
||||
max_canary_price_btc_per_eh_day=Decimal("0.52"),
|
||||
max_canary_expected_loss_btc=Decimal("0.000025"),
|
||||
min_discount_to_breakeven=Decimal("0.08"),
|
||||
min_duration_minutes=30,
|
||||
max_duration_minutes=720,
|
||||
|
|
|
|||
Loading…
Reference in a new issue