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
3 changes: 1 addition & 2 deletions .github/workflows/testnet11-asset-bootstrap-helper.yml
Original file line number Diff line number Diff line change
Expand Up @@ -277,15 +277,14 @@ jobs:
snippet_lines.append(f" signer_key_id: {yaml_quote(signer_key_id)}")
snippet_lines.append(f" receive_address: {yaml_quote(receive_address)}")
snippet_lines.append(" pricing:")
snippet_lines.append(' reference_source: "coingecko"')
snippet_lines.append(' reference_pair: "txch_usd"')
snippet_lines.append(' side: "sell"')
snippet_lines.append(f" fixed_quote_per_base: {fixed_quote_per_base}")
snippet_lines.append(" quote_unit_mojo_multiplier: 1000000000000")
snippet_lines.append(" slippage_bps: 100")
snippet_lines.append(" strategy_target_spread_bps: 140")
snippet_lines.append(" strategy_min_xch_price_usd: 20.0")
snippet_lines.append(" strategy_max_xch_price_usd: 60.0")
snippet_lines.append(" strategy_offer_expiry_minutes: 10")
snippet_lines.append(" cancel_policy_stable_vs_unstable: true")
snippet_lines.append(" inventory:")
snippet_lines.append(" low_watermark_base_units: 0")
Expand Down
Binary file added .tmp-fix-xch-pricing.bundle
Binary file not shown.
67 changes: 50 additions & 17 deletions config/markets.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,14 @@ markets:
cloud_wallet_base_global_id: "Asset_ymgm3ygl5om7ia4u9llk3iu7"
cloud_wallet_quote_global_id: "Asset_huun64oh7dbt9f1f9ie8khuw"
pricing:
reference_source: "coingecko"
reference_pair: "xch_usd"
side: "sell"
min_price_quote_per_base: 0.0031
max_price_quote_per_base: 0.0038
slippage_bps: 100
strategy_target_spread_bps: 140
strategy_min_xch_price_usd: 20.0
strategy_max_xch_price_usd: 60.0
strategy_offer_expiry_minutes: 10
cancel_policy_stable_vs_unstable: true
inventory:
low_watermark_base_units: 500
Expand Down Expand Up @@ -62,8 +61,7 @@ markets:
side: "sell"
fixed_quote_per_base: 7.75
slippage_bps: 25
strategy_offer_expiry_unit: "hours"
strategy_offer_expiry_value: 2
strategy_offer_expiry_minutes: 120
inventory:
low_watermark_base_units: 500
low_inventory_alert_threshold_base_units: null
Expand Down Expand Up @@ -99,15 +97,14 @@ markets:
cloud_wallet_base_global_id: "Asset_wjfkwih7s6c1y6rlswnznr6p"
cloud_wallet_quote_global_id: "Asset_huun64oh7dbt9f1f9ie8khuw"
pricing:
reference_source: "coingecko"
reference_pair: "xch_usd"
side: "sell"
min_price_quote_per_base: 0.0027
max_price_quote_per_base: 0.0033
slippage_bps: 100
strategy_target_spread_bps: 140
strategy_min_xch_price_usd: 20.0
strategy_max_xch_price_usd: 60.0
strategy_offer_expiry_minutes: 10
cancel_policy_stable_vs_unstable: true
inventory:
low_watermark_base_units: 500
Expand Down Expand Up @@ -147,8 +144,7 @@ markets:
side: "sell"
fixed_quote_per_base: 6.75
slippage_bps: 25
strategy_offer_expiry_unit: "hours"
strategy_offer_expiry_value: 2
strategy_offer_expiry_minutes: 120
inventory:
low_watermark_base_units: 500
low_inventory_alert_threshold_base_units: null
Expand All @@ -173,7 +169,7 @@ markets:
combine_when_excess_factor: 2.0

- id: eco1812020_sell_xch
enabled: false
enabled: true
mode: sell_only
base_asset: "e257aca547a83020e537e87f8c83e9332d2c3adb729c052e6f04971317084327"
base_symbol: "ECO.181.2020"
Expand All @@ -184,15 +180,15 @@ markets:
cloud_wallet_base_global_id: "Asset_s1ifxnpgnf8n3hsd6wlb04q5"
cloud_wallet_quote_global_id: "Asset_huun64oh7dbt9f1f9ie8khuw"
pricing:
reference_source: "coingecko"
reference_pair: "xch_usd"
side: "sell"
min_price_quote_per_base: 0.0030
max_price_quote_per_base: 0.0037
slippage_bps: 100
strategy_target_spread_bps: 140
strategy_min_xch_price_usd: 20.0
strategy_max_xch_price_usd: 60.0
strategy_offer_expiry_minutes: 10
cancel_move_threshold_bps: 300
cancel_policy_stable_vs_unstable: true
inventory:
low_watermark_base_units: 500
Expand Down Expand Up @@ -232,8 +228,7 @@ markets:
side: "sell"
fixed_quote_per_base: 7.50
slippage_bps: 25
strategy_offer_expiry_unit: "hours"
strategy_offer_expiry_value: 2
strategy_offer_expiry_minutes: 120
inventory:
low_watermark_base_units: 500
low_inventory_alert_threshold_base_units: null
Expand Down Expand Up @@ -269,15 +264,14 @@ markets:
cloud_wallet_base_global_id: "Asset_e3jexei5nswyy916lg77vabz"
cloud_wallet_quote_global_id: "Asset_huun64oh7dbt9f1f9ie8khuw"
pricing:
reference_source: "coingecko"
reference_pair: "xch_usd"
side: "sell"
min_price_quote_per_base: 0.0027
max_price_quote_per_base: 0.0033
slippage_bps: 100
strategy_target_spread_bps: 140
strategy_min_xch_price_usd: 20.0
strategy_max_xch_price_usd: 60.0
strategy_offer_expiry_minutes: 10
cancel_policy_stable_vs_unstable: true
inventory:
low_watermark_base_units: 500
Expand Down Expand Up @@ -317,8 +311,46 @@ markets:
side: "sell"
fixed_quote_per_base: 6.75
slippage_bps: 25
strategy_offer_expiry_unit: "hours"
strategy_offer_expiry_value: 2
strategy_offer_expiry_minutes: 120
inventory:
low_watermark_base_units: 500
low_inventory_alert_threshold_base_units: null
current_available_base_units: 500
bucket_counts:
1: 0
10: 0
100: 0
ladders:
sell:
- size_base_units: 1
target_count: 5
split_buffer_count: 1
combine_when_excess_factor: 2.0
- size_base_units: 10
target_count: 2
split_buffer_count: 1
combine_when_excess_factor: 2.0
- size_base_units: 100
target_count: 1
split_buffer_count: 0
combine_when_excess_factor: 2.0

- id: eco292021_sell_wusdbc
enabled: true
mode: sell_only
base_asset: "5af8db0b15e0de99ad1eff02486bb1998602053c56dfb22dc04e0f5e17ccec8d"
base_symbol: "ECO.29.2021"
quote_asset: "wUSDC.b"
quote_asset_type: stable
signer_key_id: "key-main-1"
receive_address: "xch1u3tytpv45sj0h4lpwmtkyzh2ggvw4x7jccyxzu995p2aj40wzcxqvymyn3"
cloud_wallet_base_global_id: "Asset_aihyg9ncps0bqdkyw9j7i8x2"
cloud_wallet_quote_global_id: "Asset_cxc7mql006dp2w3kigqlj58t"
pricing:
side: "sell"
fixed_quote_per_base: 6.5
slippage_bps: 25
strategy_offer_expiry_minutes: 120
inventory:
low_watermark_base_units: 500
low_inventory_alert_threshold_base_units: null
Expand Down Expand Up @@ -353,6 +385,7 @@ markets:
receive_address: "xch1u3tytpv45sj0h4lpwmtkyzh2ggvw4x7jccyxzu995p2aj40wzcxqvymyn3"
pricing:
fixed_quote_per_base: 0.999
strategy_offer_expiry_minutes: 30
inventory:
low_watermark_base_units: 200
low_inventory_alert_threshold_base_units: 200
Expand Down
6 changes: 2 additions & 4 deletions config/testnet-markets.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,14 @@ markets:
signer_key_id: "key-main-1"
receive_address: "txch1t37dk4kxmptw9eceyjvxn55cfrh827yf5f0nnnm2t6r882nkl66qknnt9k"
pricing:
reference_source: "coingecko"
reference_pair: "txch_usd"
side: "sell"
fixed_quote_per_base: 0.004714285714285714
quote_unit_mojo_multiplier: 1000000000000
slippage_bps: 100
strategy_target_spread_bps: 140
strategy_min_xch_price_usd: 20.0
strategy_max_xch_price_usd: 60.0
strategy_offer_expiry_minutes: 10
cancel_policy_stable_vs_unstable: true
inventory:
low_watermark_base_units: 500
Expand Down Expand Up @@ -53,15 +52,14 @@ markets:
signer_key_id: "key-main-1"
receive_address: "txch1t37dk4kxmptw9eceyjvxn55cfrh827yf5f0nnnm2t6r882nkl66qknnt9k"
pricing:
reference_source: "coingecko"
reference_pair: "txch_usd"
side: "sell"
fixed_quote_per_base: 0.002420604183
quote_unit_mojo_multiplier: 1000000000000
slippage_bps: 100
strategy_target_spread_bps: 140
strategy_min_xch_price_usd: 20.0
strategy_max_xch_price_usd: 60.0
strategy_offer_expiry_minutes: 10
cancel_policy_stable_vs_unstable: true
inventory:
low_watermark_base_units: 0
Expand Down
103 changes: 103 additions & 0 deletions docs/progress.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,108 @@
# Progress Log

## 2026-03-11 (parallel reservation drain timing + John-Deere worker cap set to 3)

- Diagnosed the remaining `eco1812020_sell_xch` underfill behavior on `John-Deere` as refill-drain latency in the daemon reservation queue rather than duplicate offer artifact assignment:
- strategy planning repeatedly produced full refill batches,
- reservation leases for queued items stayed active for long periods before worker pickup/release.
- Refactored parallel Cloud Wallet strategy execution in `greenfloor/daemon/main.py` so reservation acquisition happens at worker execution time instead of pre-acquiring every queued submission.
- Added `market_decision` timing instrumentation (same logger path used by `debug.log`) for:
- `parallel_offer_dispatch` (planned/queued/worker counts),
- `parallel_offer_queue_wait` (per-submission queue delay),
- `parallel_offer_reservation_acquired` / `parallel_offer_reservation_released` (acquire and hold timings).
- Updated live `John-Deere` runtime config to `runtime.offer_parallelism_max_workers: 3`, deployed the patched daemon file, restarted, and verified new debug evidence:
- `parallel_offer_dispatch ... workers=3` for `eco1812020_sell_xch`,
- queue/lease timing lines now visible in `debug.log` for active refill cycles.

## 2026-03-11 (removed ladder cadence throttling after live ECO/XCH watch)

- Re-ran live `John-Deere` monitoring for `eco1812020_sell_xch` while treating Dexie as authoritative apart from normal latency:
- repeated Dexie / SQLite snapshots showed the market oscillating below target (`1` rung dropping from `2` to `1`) while the daemon still held recently-open local rows that had not reconciled out yet,
- current underfill was therefore not a Dexie-visibility bug; the daemon was simply refilling too slowly once multiple short-TTL offers expired near each other.
- Removed the strategy/reseed cadence throttle in `greenfloor/daemon/main.py`:
- `_apply_action_cadence_gate(...)` now passes planned actions through unchanged instead of suppressing or shrinking repost batches,
- removed the now-dead recent-post cadence bookkeeping tied to `strategy_offer_execution` history.
- Updated deterministic coverage in `tests/test_daemon_offer_execution.py` to assert the new passthrough behavior instead of the old cadence-limited cases.
- Enabled `runtime.offer_parallelism_enabled` on `John-Deere` and re-watched the live market:
- the daemon now does plan the full missing burst immediately, and Cloud Wallet begins draining the burst in parallel,
- but the current submission ordering still prioritizes larger sizes first (`10` before `1`), so Dexie can remain underfilled on the critical small rung while the worker spends several minutes posting larger replacements,
- `greenfloor/cloud_wallet_offer_runtime.py` also writes per-offer `strategy_offer_execution` audit rows during posting, which explains the staggered DB evidence seen during live monitoring.
- Fixed the refill ordering in `greenfloor/daemon/main.py`:
- `_expand_strategy_actions(...)` no longer re-sorts planned actions by descending size before submission,
- strategy/reseed action order now follows the planner's natural ascending ladder order so missing `1` offers are submitted before missing `10` offers.
- Added deterministic regression coverage in `tests/test_daemon_offer_execution.py` for preserving strategy action order during expansion.

## 2026-03-10 (final-gap cadence bypass + BYC quote-balance confirmation)

- Tightened the generalized short-TTL cadence gate in `greenfloor/daemon/main.py` after reviewing the live `eco1812020_sell_xch` underfill:
- the prior generalized gate could still suppress the last missing `1` when the market sat at `4/5` and the latest same-size post was inside the spacing window,
- the gate now bypasses cadence suppression for the final missing offer (`target_count - active_count <= 1`) so the daemon can still restore the rung to target.
- Added deterministic coverage in `tests/test_daemon_offer_execution.py` for the exact `4/5 -> allow 1 more` case.
- Rechecked the live `byc_two_sided_wusdbc` buy-side failure on `John-Deere` with direct wallet evidence:
- the daemon does plan the configured buy-side offer,
- current `strategy_offer_execution` rows show the buy action is skipped by Cloud Wallet with `cloud_wallet_offer_insufficient_spendable_balance:side=buy:required=9990:available=50`,
- direct `wallet.list_coins(asset_id=Asset_cxc7mql006dp2w3kigqlj58t, include_pending={False,True})` returns exactly one unlocked settled `wUSDC.b` coin of `50`,
- so the present blocker is insufficient spendable quote inventory in the vault, not Dexie visibility.

## 2026-03-10 (daemon general strategy cadence gate + BYC buy-side finding)

- Followed up on live `John-Deere` monitoring after the first short-TTL cadence patch:
- the initial gate only affected reseed injection,
- ordinary `strategy_actions_present` planning still emitted repeated `1`-offer posts for `eco1812020_sell_xch`,
- live Dexie samples continued to oscillate (`5 -> 4 -> 3 -> 2`) and remote audit events still showed grouped `1` executions from the main strategy path.
- Generalized cadence limiting in `greenfloor/daemon/main.py`:
- the action gate now keys by `(side, size)` using recent successful `strategy_offer_execution` events,
- ordinary `below_target` actions are cadence-limited before execution, not only reseed-only actions,
- existing reseed gating now reuses the same helper so the behavior is consistent across both planning paths.
- Added deterministic coverage in `tests/test_daemon_offer_execution.py` for:
- suppressing general strategy actions when the most recent same-side/same-size post is still inside the cadence window,
- reducing ordinary repeated `below_target` actions to a single post once the cadence window has elapsed.
- Separate live finding on `byc_two_sided_wusdbc`:
- the daemon repeatedly observed `buy: 0 / sell: 3`, planned one buy-side action, and still finished cycles with `strategy_executed=0`,
- current local builder path explicitly skips buy-side offers with `offer_builder_failed:buy_side_requires_cloud_wallet_path`,
- this is a distinct two-sided BYC defect and is not fixed by the ECO/XCH cadence change.

## 2026-03-10 (daemon small-offer reseed cadence gating)

- Hardened sell-only reseed behavior in `greenfloor/daemon/main.py` to reduce bursty `1`-offer reposting on short-TTL markets:
- repeated same-size reseed actions now consult recent successful `strategy_offer_execution` timestamps for the market,
- small repeated ladders (`target_count >= 3`) are cadence-limited to approximately one spacing interval at a time instead of reposting the full deficit in a single cycle,
- cold-start / fully empty ladders still permit a small bootstrap burst (`2`) so markets do not stay empty for the full spacing window.
- Goal: keep `10`-minute `1`-offer ladders, especially `eco1812020_sell_xch`, from expiring in synchronized bursts that temporarily drop live Dexie coverage below target even though the daemon later refills the rung.
- Added deterministic coverage in `tests/test_daemon_offer_execution.py` for:
- empty-market bootstrap reseed limiting,
- suppression when the most recent successful same-size post is still inside the cadence window,
- resuming a single small-offer refill after the cadence window elapses.

## 2026-03-10 (Dexie 404 reconciliation fix for stale ECO 50 offers)

- Root cause confirmed on `John-Deere` for `eco1812020_sell_xch` not reposting its `50` rung:
- Dexie no longer returned several watched `50` offer ids,
- direct `DexieAdapter.get_offer(...)` on those ids returned `HTTP Error 404: Not Found`,
- daemon reconciliation left the prior `offer_state` rows at `open`, so strategy kept counting `50: 1` and suppressed replacement posting.
- Fixed `greenfloor/daemon/main.py` reconciliation for watched offers that vanish from Dexie:
- direct watched-offer fetches that 404 now transition the local offer state to `expired`,
- ordinary direct-fetch network failures still remain non-terminal and do not clear active state.
- Added deterministic regression coverage in `tests/test_daemon_offer_execution.py` for the exact stale-watch scenario:
- watched offer present in local state,
- Dexie list omits it,
- direct `get_offer()` 404 forces the row out of the active set.
- Live remediation on `John-Deere`:
- identified stale open `50` offer rows for `eco1812020_sell_xch`,
- prepared cleanup so the market can resume posting replacement `50` offers immediately instead of waiting for manual DB intervention.

## 2026-03-10 (upstream ent-wallet duplicate vault CAT input report + workaround analysis)

- Documented a new upstream Cloud Wallet / `ent-wallet` issue draft in `docs/ent-wallet-upstream-duplicate-vault-cat-offer-inputs.md`:
- live `John-Deere` evidence shows malformed `50`-unit ECO vault offers already contain duplicate CAT inputs before `greenfloor` validates the returned `offer1...`,
- Cloud Wallet `CREATE_OFFER` transactions themselves report the same CAT coin id multiple times in `inputs`,
- likely root cause is the row-multiplying `transactionCoinRecords` join in `getUnspentVaultCoinsByWalletId(...)`.
- Captured temporary workaround options while waiting for the upstream fix:
- safest immediate mitigation is pausing the `50` rung,
- more targeted operational stopgap is combining explicit clean ECO inputs into a fresh exact `50000`-mojo CAT coin,
- off-chain cancellation of malformed internal wallet offers may free locked polluted inputs,
- a possible future `greenfloor` mitigation is a cooldown and/or exact-input-combine path when `wallet_sdk_offer_duplicate_spent_coin_ids` recurs.

## 2026-03-05 (BYC scoped-query leak fail-closed mitigation + John-Deere validation)

- Root cause for the remaining BYC coin-op failure on John-Deere was narrowed further:
Expand Down
32 changes: 31 additions & 1 deletion greenfloor/adapters/cloud_wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,33 @@ def list_coins(
break
return coins

def get_chia_usd_quote(self) -> float:
query = """
query quote($asset: String!) {
quote(asset: $asset) {
price
baseAsset
currency
source
createdAt
}
}
"""
payload = self._graphql(query=query, variables={"asset": "chia"})
quote_payload = payload.get("quote")
if not isinstance(quote_payload, dict):
raise RuntimeError("cloud_wallet_missing_quote")
raw_price = quote_payload.get("price")
if not isinstance(raw_price, str | int | float):
raise RuntimeError("cloud_wallet_invalid_quote_price")
try:
price = float(raw_price)
except (TypeError, ValueError) as exc:
raise RuntimeError("cloud_wallet_invalid_quote_price") from exc
if price <= 0:
raise RuntimeError("cloud_wallet_invalid_quote_price")
return price

def split_coins(
self,
*,
Expand Down Expand Up @@ -414,6 +441,9 @@ def get_wallet(
states: list[str] | None = None,
first: int = 100,
) -> dict[str, Any]:
# Cloud Wallet currently rejects wallet.offers limits above 100.
# Revisit this guard if Cloud Wallet pagination defaults/maxima change.
first_limit = min(100, max(0, int(first)))
query = """
query getWallet($walletId: ID, $isCreator: Boolean, $states: [OfferState!], $first: Int) {
wallet(id: $walletId) {
Expand All @@ -439,7 +469,7 @@ def get_wallet(
"walletId": self._vault_id,
"isCreator": is_creator,
"states": states,
"first": int(first),
"first": first_limit,
},
)
wallet = payload.get("wallet") or {}
Expand Down
Loading
Loading