Ringkasan perubahan di branch feat/skew-spread-config, dirangkum dari PRD
“PRD – Skew Profile Surya OZ Hub” v2.0.0 dan diff aktual di GitLab. Satu edit spread di Risk
Dashboard kini menjangkau seluruh profile di Surya Hub dalam satu push — tanpa replikasi manual Dealing.
Setiap perubahan Spread Configuration hanya diterapkan ke Default Profile. Dealing harus menyalin manual ke tiap profile lain di OZ Hub. Kalau lupa, skew di profile itu basi — spread yang dilihat client bisa beda antar profile, dan gak ada yang kasih tau.
Satu perubahan Spread Config diterapkan ke semua Settings Profile yang ada dalam satu push. Skew identik di semua profile, gak peduli profile mana yang aktif. Match band dirombak biar gak ambigu, dan hasil push diklasifikasi + di-notify lewat Lark.
Trade trigger di Risk Dashboard → klasifikasi band → fan-out ke semua profile → satu Batch Query ke Hub → asynchronus response → roll-up Queue History → Lark kalau gagal.
Trade masuk, Risk Dashboard cocokkan ukuran trade ke band Operator + Lot untuk Core Symbol & Taker Stream ID yang sesuai. Band cuma hidup di Risk Dashboard — Hub gak pernah ditanya soal band.
Operator: ≥ 0 · Lot: 1Arah trigger (buy/sell) menentukan pasangan markup mana yang jadi bid/ask spread points. Konfigurasi simpan tetap, tanpa save-and-push, tanpa popup.
Buy Markup Bid -2 / Ask 2Satu call membaca semua Settings Profile + rules-nya. Default profile gak ada di endpoint profiles — dibaca terpisah lalu disisipkan sebagai pseudo-profile.
Rule dicari per profile memakai coreSymbol + priceChannelId (taker stream) — kunci match unik, gak ambigu. Ketemu → target tulis; gak ketemu → skip + catat.
applied_in[] / missing_in[]Satu baris feature_queues mewakili seluruh push, diberi push_group_id (UUID). Semua outcome per profile ditulis ke spread_queue_histories di awal.
Semua profile yang match jadi sub-query PATCH dalam satu POST /api/rest/settings. Sekali tulis, berapa pun jumlah profile. Hub-side transaksional.
Worker nunggu response async, pairing response[i] ↔ profile_map[i], update status per profile, lalu roll-up jadi satu status push.
Queue History nampilin satu baris per push dengan breakdown applied/missing. Lark di-fire kalau Partially Failed / Failed — Completed di-silent.
GET /api/spread-queue-history/{groupKey}Endpoint baru edit-spread-all-profiles. Behavior edit config tetap persis seperti
hari ini — Hub cuma ditulis oleh trigger. Skew sebelumnya di-overwrite, gak ada yang dibuat di Hub.
coreSymbol + priceChannelId saja.Trigger kedua untuk band yang sama boleh jalan begitu push sebelumnya sudah terkirim. Kalau response push lama masih pending saat trigger baru datang, response lama dianggap basi dan dibuang — hanya outcome push terbaru yang dicatat.
dedupe_key = driver|coreSymbol|priceChannelId,
karena payload batch berupa array dan semua batch nembak URL yang sama.History di-grup per push_group_id — satu trigger = satu baris, lengkap dengan
hitungan profile total / applied / failed / pending. Baris legacy (tanpa group) tetap tampil sendiri.
GET /api/spread-queue-history/{groupKey} buat expand detail per profile.Tiga format alert, dikirim lewat profile notification
one_zero_spread_update_all_profile di rv3_system.notification_profiles.
Alert dikirim setelah Hub menjawab (atau langsung kalau gak ada target sama sekali), biar
satu push cuma bikin satu pesan.
Pause manual maupun jadwal otomatis kini kirim pesan ke group VT Dealing Ops berisi
waktu dan siapa yang pause/resume. Email auto-pause NOTIF_AUTO_PAUSE_OZ_QUEUE lama
sudah dihapus dari jalur schedule.
name → preferred_username → email), terakhir Unknown.Enum SkewPushStatus baru (completed / partially_failed / failed) plus nama queue
baru priceChannelRuleSpreadAllProfiles dengan prioritas 1 — setara queue spread lama,
di atas priceChannelRule (prioritas 2).
Dihitung dari jumlah profile applied / failed / pending. Selama masih ada yang pending,
statusnya processing — roll-up final baru keluar setelah semua row selesai.
| Status | Kapan | Dampak |
|---|---|---|
| COMPLETED | Rule match di semua profile dan semua tulisan sukses. | Satu baris history. Tanpa notifikasi Lark. |
| PARTIALLY FAILED | Symbol hilang di sebagian profile (bukan semua). Profile yang punya rule tetap di-update. | History + Lark alert “Applied in: … / Missing in: …”. Skew di profile yang missing beda dengan yang lain sampai rule dibuat di client oneZero. |
| FAILED | Rule hilang di semua profile (Missing Profile → abort), atau gagal koneksi / timeout / tidak ada response. | History + Lark alert URGENT. Trade tetap memakai skew lama yang sudah live. Tidak ada auto-retry. |
| PROCESSING | Masih ada row profile berstatus unprocessed/processing. |
Tampil di list selama worker belum selesai; job lebih lama dari stuck_processing_secs (default 120s) otomatis di-reclaim. |
FAILED dan alert dikirim di saat itu juga, karena memang
tidak akan ada response yang bisa ditunggu.Field bersama tiap alert: Core Symbol, Taker Stream ID, Direction, Band (Operator + Lot),
dan Skew sent (Bid/Ask). Penanda Pushes affected in this window: [count] berasal dari PRD.
// group: General Notifications · color: orange ⚠️ [Surya] Skew not applied - EUR/USD missing in Profile Roll 2 **Core Symbol:** EUR/USD **Taker Stream ID:** TAKER-EURUSD **Direction:** Buy **Band:** ≥ 0 1 **Skew sent:** Bid -2, Ask 2 **Applied in:** Default, Profile Roll **Missing in:** Profile Roll 2 The skew in the profiles listed as missing is out of line with the rest until the rule is created in oneZero client.
// color: red ⚠️ [Surya] URGENT - skew not applied at all, EUR/USD missing in all profiles **Core Symbol:** EUR/USD **Taker Stream ID:** TAKER-EURUSD **Direction:** Sell **Band:** ≤ -75 **Skew sent:** Bid -2, Ask 2 **Applied in:** none **Missing in:** Default, Profile Roll, Profile Roll 2 The trade priced on whatever skew was already live in the Hub.
⚠️ [Surya] Skew push failed - EUR/USD
**Core Symbol:** EUR/USD
**Taker Stream ID:** TAKER-EURUSD
**Direction:** Buy
**Band:** ≥ 100
**Skew sent:** Bid -4, Ask 4
**Failed profiles:** Default, Profile Roll
**Error:** no response received
Batch bersifat all-or-nothing: kalau satu sub-query gagal, seluruh batch di-rollback dan semua target profile dilaporkan gagal.
// color: red (pause) / green (resume) Surya Hub Queue Paused **Paused at:** 2026-10-03 14:05:00 **Paused by:** Budi Santiko Surya Hub Queue Resumed // profile: one_zero_queue_status_vt_dealing_ops
Dikirim dari toggle pause manual (PATCH /api/queue/status)
dan dari pause jadwal otomatis di worker — bukan cuma dari UI.
| Method | Path | Status | Keterangan |
|---|---|---|---|
| PATCH | /{driver}/price-channel-rule-jobs/edit-spread-all-profiles | BARU | Push bid/ask spread ke rule yang match di semua Settings Profile. Balasan berisi
pre-flight classification + push_group_id + applied_in/missing_in. |
| GET | /api/spread-queue-history/{groupKey} | BARU | Breakdown per profile untuk satu push group (expand dari baris list). Legacy row pakai id-nya sendiri. |
| GET | /api/spread-queue-history | UBAH | Dari list mentah per baris → di-roll-up jadi satu baris per push + agregat + status terklasifikasi. |
| PATCH | /api/queue/status | UBAH | Toggle pause/resume sekarang juga menembak notifikasi Lark VT Dealing Ops. |
| PATCH | /{driver}/price-channel-rule-jobs/edit-spread | UBAH | Tetap ada — jalur lama untuk profile Default saja. |
edit-spread-all-profiles{
"requestId": "req-uuid",
"coreSymbol": "EUR/USD",
"priceChannelId": "TAKER-EURUSD", // taker stream
"direction": "buy", // buy | sell
"bidSpreadPoints": -2,
"askSpreadPoints": 2,
// konteks band, direkam ke queue history untuk rekonsiliasi
"config_operator": ">= 0",
"config_lot": 1,
"config_buy_markup_bid": -2, "config_buy_markup_ask": 2,
"config_sell_markup_bid": -2, "config_sell_markup_ask": 2
}
ozhub)| File | Kolom | Fungsi |
|---|---|---|
| 2026_08_04_000000_add_profile_to_spread_queue_histories_table | profile_id, profile_name, push_group_id, direction |
Granularitas per profile untuk satu push, plus pengelompok push dan arah trigger. |
| 2026_08_04_000100_add_profile_id_to_feature_queue_details_table | profile_id | Mengaitkan tiap detail queue dengan history profile-nya. |
| 2026_09_26_000000_add_batch_to_feature_queue_details_table | profile_map (json), dedupe_key (191, idx) |
profile_map: daftar berurutan {profile_id, profile_name, rule_id} supaya
response[i] bisa dipasangkan dengan target ke-i, plus daftar profile yang missing.
dedupe_key: identitas stabil push target buat aturan konkurensi. |
Repo utama sam/risk/backend/onezero
(11 commit, head 37742e7d, 02 Okt 2026). Lapisan SDK-nya di
sam/risk/backend/sdk/risk-v3 ada di
sub-bagian bawah — 4 file hasil 4 MR.
| File | Status | Isi perubahannya |
|---|---|---|
| app/Enums/SkewPushStatus.php | baru | Enum klasifikasi hasil push: completed / partially_failed / failed. |
| app/Enums/QueueName.php | ubah | Tambah PRICE_CHANNEL_RULE_SPREAD_ALL_PROFILES. |
| app/Http/Requests/…/EditSpreadAllProfilesRequest.php | baru | Validasi request push: coreSymbol, priceChannelId, direction (buy/sell), bid/ask points, plus konteks band config_*. |
| app/Http/Controllers/PriceChannelRuleJobsController.php | ubah | Action baru editSpreadAllProfiles() yang me-return klasifikasi pre-flight + daftar profile. |
| app/Http/Controllers/SpreadQueueHistoryController.php | ubah | Action baru detail($groupKey) untuk breakdown per profile. |
| app/Services/OneZeroHub/PriceChannelRuleJobs.php | ubah | Inti fitur. editPriceChannelRuleSpreadAllProfiles(): baca semua profile + Default, match per profile, klasifikasi, tulis queue + history, enqueue satu batch, alert kalau gak ada target. |
| app/Services/OneZeroHub/PriceChannelRule.php | ubah | Method baru profiles() dan profileSettings(); semua request pindah ke opsi HTTP terpusat. |
| app/Services/OneZeroHub/QueueCategories/QueuePriceChannelRule.php | ubah | Worker: proses payload batch, debounce stale per dedupe_key, parse hasil batch per profile, kirim alert, kembalikan row missing ke status failed. |
| app/Services/OneZeroHub/OneZeroQueue.php | ubah | Guard supaya job multi-profile tidak menimpa status history secara massal; reclaim job nyangkut di processing; pause jadwal kirim Lark (email auto-pause dihapus). |
| app/Services/SkewNotifier.php | baru | Semua template Lark: missingInSomeProfiles(), missingInAllProfiles(), pushFailed(), queuePauseResumed(); resolve webhook+secret dari notification_profiles. |
| app/Services/SpreadQueueHistoryService.php | ubah | Query list jadi agregat per push_group_id, roll-up status, hitung applied/missing, plus filter HAVING. |
| app/Services/QueueService.php | ubah | editStatus() memanggil notifier pause/resume dengan nama user. |
| app/Services/OneZeroHub/Config.php | ubah | httpOptions() terpusat: verify off, timeout, Connection: close, backoff retry. |
| app/Services/OneZeroHub/QueuePriorities/… | ubah | Queue baru dapat prioritas 1. |
| config/onezero.php | ubah | Blok http (connect_timeout 5s, timeout 30s, force_close, retry_backoff 500ms) + stuck_processing_secs 120. |
| routes/api.php | ubah | Dua route baru: edit-spread-all-profiles dan spread-queue-history/{groupKey}. |
| database/migrations/onezero/*.php (×3) | baru | Lihat bagian skema di atas. |
| docs/API.md | ubah | Dokumentasi endpoint baru + penjelasan perilaku batch (non-transactional per profile di sisi kita, transaksional di sisi Hub). |
Branch sama, feat/skew-spread-config, target develop, head 21f0174c (21 Sep 2026).
Empat MR, empat file — semua perubahan yang menyentuh jalur skew/pause di SDK:
| File | MR | Status | Isi perubahannya |
|---|---|---|---|
| src/OneZero/OneZeroJobsService.php | !99 | merge | Method baru editSpreadAllProfiles(company, body, version?) — PATCH {host}/api/{company}/price-channel-rule-jobs/edit-spread-all-profiles,
timeout 30s, throwIfServerError() + throwIfClientError() jadi error HTTP langsung meledak ke pemanggil.
Ini satu-satunya pintu SDK ke endpoint push multi-profile — belum ada method lain yang dipakai buat push skew. |
| src/OneZero/OneZeroSpreadQueueHistoryService.php | !100 | merge | Method baru detail(groupKey) — GET /api/spread-queue-history/{groupKey}.
groupKey = push_group_id, atau id baris sendiri untuk row legacy.
Dipakai buat expand satu baris roll-up history jadi rincian per profile. |
| src/OneZero/OneZeroQueueService.php | !101 | merge | editStatus(): ambil auth('keycloak')->user(), tulis log queue_update_logs (pause/unpause),
lalu kirim field user (name → email) ke OZ Hub. Komentarnya eksplisit: supaya alert Lark
VT Dealing Ops bisa nyebut siapa yang pause/resume (PRD Req 6) — bukan cuma statusnya. |
| src/OneZero/OneZeroQueueService.php | !106 | open | Perbaikan dari MR !101: $user?->getAuthIdentifier() (user null gak bikin 500 pas logging) plus
resolveActingUser() — urutan fallback model → klaim JWT name → preferred_username → email → Unknown.
Token dibaca via decodeTokenClaims() (base64 payload tanpa re-verify, guard sudah validasi) karena
guard keycloak resolve User lokal by email sehingga name sering kosong walau token bawa username. |
Catatan: diff mentah master...feat/skew-spread-config di SDK kebanyakan noise — branch itu diturunkan dari
develop, jadi ikut bawa commit Person resolver, Retry/export, mailer, dll. Yang di atas adalah 4 file hasil
4 MR yang memang terkait PRD ini (dibaca dari tiap MR, bukan dari diff mentah).
Worker prosesnya panjang dan bicara ke stunnel lokal yang mendaur ulang tunnel idle. Tiap request
kini pakai Connection: close + timeout eksplisit supaya socket mati langsung gagal, bukan
menggantung. Retry pakai backoff yang bisa diatur via OZ_HTTP_RETRY_BACKOFF_MS.
processing di-reclaimRunner cuma ambil baris unprocessed, jadi job yang mati di tengah akan memblokir
antrean selamanya. Worker kini me-reset job yang stuck > stuck_processing_secs
(default 120s) kembali ke unprocessed, termasuk detail yang statusnya
processing — log warning sebagai penanda.
Job multi-profile punya status per profile (sebagian processed, sebagian
failed). Guard ditambahkan di dua titik tulis — saat pick-up dan saat selesai —
supaya update massal tidak mengembalikan semua row ke processing selamanya. Ini bug yang
diperbaiki di commit terakhir 37742e7d.
Sebelumnya satu detail queue per profile. Sekarang satu feature_queue_details membawa
array sub-query PATCH — jumlah request ke Hub konstan berapa pun banyaknya profile. Karena payload
berupa array, kunci debounce lama (payload + url) tidak bisa dipakai, makanya ada dedupe_key.
Alert tidak ditembak saat enqueue — ditunggu sampai Hub menjawab, supaya masalah “rule hilang” dan “push ditolak” menyatu dalam satu pesan. Pengecualian: kalau gak ada profile yang match, memang tidak akan ada response, jadi alert langsung dikirim saat itu juga.
FAILED + alert.| # | Tipe | Kasus | Ekspektasi |
|---|---|---|---|
| P1 | positif | Buy EUR/USD, Lot 1 → match Operator ≥ 0 (Bid -2 / Ask 2) |
Bid Spread -2, Ask Spread 2, Queue History = Completed |
| P2 | positif | Sell EUR/USD, Lot 1 → match Operator ≥ 0 (Sell markup) |
Bid Spread -2, Ask Spread 2, Queue History = Completed |
| P4 | positif | Export CSV → edit threshold offline → import file valid | Spread Config tersimpan; Hub tidak tersentuh sampai trigger berikutnya |
| P5/6 | positif | Pause / Resume Queue Hub | Lark notification terkirim (VT Dealing Ops) |
| N1 | negatif | EUR/USD band ≥ 200 hilang di Profile Roll 2 (ada di Default + Profile Roll) | Bid -4 / Ask 4 diterapkan di 2 profile pertama, di-skip di Profile Roll 2 → status Partially Failed + alert “Applied in / Missing in” |
| N2 | negatif | EUR/USD band ≤ -75 tidak ada di profile manapun | Tidak ada yang diterapkan, trade memakai skew lama → status Failed (Missing Profile) + alert “Applied in: none” |
| N3 | negatif | oneZero API timeout saat push band ≥ 100 | Status Failed (no response received) + alert dengan pesan error, tanpa auto-retry |
| N4 | ingat | Update band EUR/USD ≥ 200 lalu Save | Perubahan tidak diterapkan sampai trigger berikutnya — tidak ada save-and-push |
sam/risk/qa/risk-management-playwright) yang nyentuh branch ini, tabel ini bisa dilengkapi
dengan bukti eksekusi.