melaya 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE +158 -0
- data/README.md +244 -163
- data/lib/melaya/account.rb +30 -30
- data/lib/melaya/accounts.rb +42 -0
- data/lib/melaya/assistant.rb +41 -0
- data/lib/melaya/auth.rb +137 -0
- data/lib/melaya/backtest.rb +139 -101
- data/lib/melaya/billing.rb +75 -0
- data/lib/melaya/bugs.rb +67 -0
- data/lib/melaya/connectors.rb +71 -0
- data/lib/melaya/credentials.rb +255 -0
- data/lib/melaya/errors.rb +49 -15
- data/lib/melaya/evals.rb +78 -0
- data/lib/melaya/events.rb +425 -0
- data/lib/melaya/hitl.rb +114 -0
- data/lib/melaya/http_client.rb +226 -97
- data/lib/melaya/market.rb +198 -156
- data/lib/melaya/namespaces.rb +115 -0
- data/lib/melaya/phone.rb +75 -0
- data/lib/melaya/pipelines.rb +342 -0
- data/lib/melaya/projects.rb +58 -0
- data/lib/melaya/runner.rb +44 -0
- data/lib/melaya/sim.rb +110 -110
- data/lib/melaya/strategies.rb +170 -155
- data/lib/melaya/stream.rb +338 -336
- data/lib/melaya/team.rb +102 -0
- data/lib/melaya/templates.rb +148 -0
- data/lib/melaya/trade.rb +168 -168
- data/lib/melaya/version.rb +1 -1
- data/lib/melaya.rb +310 -79
- data/melaya.gemspec +28 -23
- metadata +32 -8
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Melaya
|
|
4
|
+
# Accounts API — GDPR data export, API key management, and profile updates.
|
|
5
|
+
#
|
|
6
|
+
# Maps to /api/v1/private/accounts/* and /api/v1/private/keys/*.
|
|
7
|
+
#
|
|
8
|
+
# Note: credit balance endpoints live in BillingAPI to keep billing concerns
|
|
9
|
+
# co-located. Key listing is in AccountAPI (trading plane) for backward
|
|
10
|
+
# compatibility; this module covers the write/management side.
|
|
11
|
+
class AccountsAPI
|
|
12
|
+
def initialize(http)
|
|
13
|
+
@http = http
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# POST /api/v1/private/accounts/export
|
|
17
|
+
# GDPR Art 15/20 data export; returns JSON blob of all user data.
|
|
18
|
+
def export_data
|
|
19
|
+
@http.post("/api/v1/private/accounts/export")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# DELETE /api/v1/private/keys/:keyId
|
|
23
|
+
# Remove a stored CEX API key.
|
|
24
|
+
# @param key_id [String]
|
|
25
|
+
def remove_key(key_id)
|
|
26
|
+
@http.delete("/api/v1/private/keys/#{enc(key_id)}")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# PATCH /api/v1/private/accounts/profile
|
|
30
|
+
# Update user display name, avatar, or settings.
|
|
31
|
+
# @param body [Hash]
|
|
32
|
+
def update_profile(body = {})
|
|
33
|
+
@http.patch("/api/v1/private/accounts/profile", body)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def enc(s)
|
|
39
|
+
URI.encode_www_form_component(s.to_s)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Melaya
|
|
4
|
+
# Assistant API — get and save the caller's onboarding / persona profile.
|
|
5
|
+
#
|
|
6
|
+
# The profile is stored envelope-encrypted (service=assistant_profile) and
|
|
7
|
+
# used to personalise the in-app assistant experience.
|
|
8
|
+
#
|
|
9
|
+
# Maps to /api/v1/private/assistant/profile.
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# profile = melaya.assistant.get_profile
|
|
13
|
+
# melaya.assistant.set_profile(name: "Antoine", goals: ["grow my trading edge"])
|
|
14
|
+
class AssistantAPI
|
|
15
|
+
def initialize(http)
|
|
16
|
+
@http = http
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# GET /api/v1/private/assistant/profile
|
|
20
|
+
# Get the caller's assistant onboarding profile.
|
|
21
|
+
def get_profile
|
|
22
|
+
@http.get("/api/v1/private/assistant/profile")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# PUT /api/v1/private/assistant/profile
|
|
26
|
+
# Save the caller's assistant onboarding profile.
|
|
27
|
+
# @param name [String, nil]
|
|
28
|
+
# @param goals [Array<String>, nil]
|
|
29
|
+
# @param context [String, nil]
|
|
30
|
+
# @param preferences [Hash, nil]
|
|
31
|
+
# @param extra [Hash] any additional profile fields
|
|
32
|
+
def set_profile(name: nil, goals: nil, context: nil, preferences: nil, **extra)
|
|
33
|
+
profile = extra.transform_keys(&:to_s)
|
|
34
|
+
profile["name"] = name unless name.nil?
|
|
35
|
+
profile["goals"] = goals unless goals.nil?
|
|
36
|
+
profile["context"] = context unless context.nil?
|
|
37
|
+
profile["preferences"] = preferences unless preferences.nil?
|
|
38
|
+
@http.put("/api/v1/private/assistant/profile", profile)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
data/lib/melaya/auth.rb
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Melaya
|
|
4
|
+
# Auth API — login, MFA, registration, password management, and session tokens.
|
|
5
|
+
#
|
|
6
|
+
# Most endpoints are public (no Bearer token needed for login/register).
|
|
7
|
+
# However, the SDK HttpClient always sends the mk_* API key which is fine —
|
|
8
|
+
# the server simply ignores it for public routes.
|
|
9
|
+
#
|
|
10
|
+
# Maps to /api/v1/private/auth/* and /api/v1/auth/*.
|
|
11
|
+
class AuthAPI
|
|
12
|
+
def initialize(http)
|
|
13
|
+
@http = http
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# POST /api/v1/private/auth/login
|
|
17
|
+
# Password + username login; returns session JWT + optional MFA challenge token.
|
|
18
|
+
# @param username [String]
|
|
19
|
+
# @param password [String]
|
|
20
|
+
def login(username:, password:)
|
|
21
|
+
@http.post("/api/v1/private/auth/login",
|
|
22
|
+
"username" => username,
|
|
23
|
+
"password" => password)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# POST /api/v1/private/auth/mfa/verify
|
|
27
|
+
# Resolve MFA challenge after login.
|
|
28
|
+
# @param challenge_token [String]
|
|
29
|
+
# @param code [String] TOTP or recovery code
|
|
30
|
+
def verify_mfa(challenge_token:, code:)
|
|
31
|
+
@http.post("/api/v1/private/auth/mfa/verify",
|
|
32
|
+
"challengeToken" => challenge_token,
|
|
33
|
+
"code" => code)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# POST /api/v1/private/auth/register
|
|
37
|
+
# Create a new user account; sends email verification.
|
|
38
|
+
def register(username:, email:, password:)
|
|
39
|
+
@http.post("/api/v1/private/auth/register",
|
|
40
|
+
"username" => username,
|
|
41
|
+
"email" => email,
|
|
42
|
+
"password" => password)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# POST /api/v1/private/auth/verify-signup
|
|
46
|
+
# Confirm email address from signup link token.
|
|
47
|
+
# @param token [String]
|
|
48
|
+
def verify_signup(token:)
|
|
49
|
+
@http.post("/api/v1/private/auth/verify-signup", "token" => token)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# POST /api/v1/private/auth/resend-verification
|
|
53
|
+
# Re-send email verification message.
|
|
54
|
+
# @param email [String]
|
|
55
|
+
def resend_verification(email:)
|
|
56
|
+
@http.post("/api/v1/private/auth/resend-verification", "email" => email)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# GET /api/v1/private/auth/me
|
|
60
|
+
# Return current authenticated user profile.
|
|
61
|
+
def me
|
|
62
|
+
@http.get("/api/v1/private/auth/me")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# GET /api/v1/private/auth/check
|
|
66
|
+
# Lightweight session validity check; returns ok: true.
|
|
67
|
+
def check
|
|
68
|
+
@http.get("/api/v1/private/auth/check")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# POST /api/v1/private/auth/change-password
|
|
72
|
+
# Change password (current + new).
|
|
73
|
+
# @param current_password [String]
|
|
74
|
+
# @param new_password [String]
|
|
75
|
+
def change_password(current_password:, new_password:)
|
|
76
|
+
@http.post("/api/v1/private/auth/change-password",
|
|
77
|
+
"currentPassword" => current_password,
|
|
78
|
+
"newPassword" => new_password)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# POST /api/v1/private/auth/forgot-password
|
|
82
|
+
# Initiate password reset flow; sends email with reset token.
|
|
83
|
+
# @param email [String]
|
|
84
|
+
def forgot_password(email:)
|
|
85
|
+
@http.post("/api/v1/private/auth/forgot-password", "email" => email)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# POST /api/v1/private/auth/reset-password
|
|
89
|
+
# Complete password reset using token from email.
|
|
90
|
+
# @param token [String]
|
|
91
|
+
# @param new_password [String]
|
|
92
|
+
def reset_password(token:, new_password:)
|
|
93
|
+
@http.post("/api/v1/private/auth/reset-password",
|
|
94
|
+
"token" => token,
|
|
95
|
+
"newPassword" => new_password)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# POST /api/v1/private/auth/mobile-handoff
|
|
99
|
+
# Create a short-lived handoff token for mobile app deep-link auth.
|
|
100
|
+
def mobile_handoff
|
|
101
|
+
@http.post("/api/v1/private/auth/mobile-handoff")
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# GET /api/v1/private/auth/permissions
|
|
105
|
+
# Return caller's permission flags (capabilities, tier, feature gates).
|
|
106
|
+
def permissions
|
|
107
|
+
@http.get("/api/v1/private/auth/permissions")
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# POST /api/v1/private/auth/refresh
|
|
111
|
+
# Rotate session JWT (sliding expiry); returns new token.
|
|
112
|
+
def refresh
|
|
113
|
+
@http.post("/api/v1/private/auth/refresh")
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# ── MFA ───────────────────────────────────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
# GET /api/v1/private/mfa/status
|
|
119
|
+
# Return MFA enrollment status for the caller.
|
|
120
|
+
def mfa_status
|
|
121
|
+
@http.get("/api/v1/private/mfa/status")
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# POST /api/v1/private/mfa/setup
|
|
125
|
+
# Initiate TOTP setup; returns QR/secret.
|
|
126
|
+
def mfa_setup
|
|
127
|
+
@http.post("/api/v1/private/mfa/setup")
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# POST /api/v1/private/mfa/confirm
|
|
131
|
+
# Confirm TOTP setup with first valid code; activates MFA.
|
|
132
|
+
# @param code [String]
|
|
133
|
+
def mfa_confirm(code:)
|
|
134
|
+
@http.post("/api/v1/private/mfa/confirm", "code" => code)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
data/lib/melaya/backtest.rb
CHANGED
|
@@ -1,101 +1,139 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Melaya
|
|
4
|
-
# Backtest API — run strategies against historical data on the Rust engine.
|
|
5
|
-
#
|
|
6
|
-
# Start a single run or a parameter sweep (grid / random), poll the job,
|
|
7
|
-
# then pull metrics, the equity curve, and the trade list. All backtests run
|
|
8
|
-
# natively on Melaya's in-house engine — no per-venue SDK in the loop.
|
|
9
|
-
#
|
|
10
|
-
# Maps to https://api.melaya.org/api/v1/private/backtest/* on the private plane.
|
|
11
|
-
class BacktestAPI
|
|
12
|
-
def initialize(http)
|
|
13
|
-
@http = http
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
# Start a backtest. +mode+ defaults to a single run; pass "grid_sweep" /
|
|
17
|
-
# "random_sweep" with +param_ranges+ to fan out a parameter search.
|
|
18
|
-
# Returns a hash with "job_id" (and optionally "count").
|
|
19
|
-
#
|
|
20
|
-
# @param body [Hash] see BacktestStart type in the reference SDKs
|
|
21
|
-
def start(body)
|
|
22
|
-
@http.post("/api/v1/private/backtest/start", body)
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
# Job status + progress (+status+, +progress_pct+, ...).
|
|
26
|
-
# @param job_id [String]
|
|
27
|
-
def job(job_id)
|
|
28
|
-
@http.get("/api/v1/private/backtest/jobs/#{job_id}")
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
# Metrics, equity curve, and OHLCV for a completed job.
|
|
32
|
-
# @param job_id [String]
|
|
33
|
-
def results(job_id)
|
|
34
|
-
@http.get("/api/v1/private/backtest/results/#{job_id}")["result"]
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# The trade list for a completed job (default 500, max 5000 per call).
|
|
38
|
-
# @param job_id [String]
|
|
39
|
-
# @param limit [Integer, nil]
|
|
40
|
-
# @param offset [Integer, nil]
|
|
41
|
-
def trades(job_id, limit: nil, offset: nil)
|
|
42
|
-
@http.get("/api/v1/private/backtest/trades/#{job_id}",
|
|
43
|
-
"limit" => limit, "offset" => offset
|
|
44
|
-
)["trades"]
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# Ranked children of a sweep parent (default objective: sharpe DESC).
|
|
48
|
-
# @param parent_id [String]
|
|
49
|
-
# @param objective [String, nil]
|
|
50
|
-
# @param limit [Integer, nil]
|
|
51
|
-
def sweep(parent_id, objective: nil, limit: nil)
|
|
52
|
-
@http.get("/api/v1/private/backtest/sweep/#{parent_id}",
|
|
53
|
-
"objective" => objective, "limit" => limit
|
|
54
|
-
)
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
# Your backtest jobs, newest first.
|
|
58
|
-
# @param limit [Integer, nil]
|
|
59
|
-
# @param offset [Integer, nil]
|
|
60
|
-
def list(limit: nil, offset: nil)
|
|
61
|
-
@http.get("/api/v1/private/backtest",
|
|
62
|
-
"limit" => limit, "offset" => offset
|
|
63
|
-
).dig("data", "jobs")
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
# Your favorited backtest jobs (Forge tier and above).
|
|
67
|
-
# @param limit [Integer, nil]
|
|
68
|
-
# @param offset [Integer, nil]
|
|
69
|
-
def favorites(limit: nil, offset: nil)
|
|
70
|
-
@http.get("/api/v1/private/backtest/favorites",
|
|
71
|
-
"limit" => limit, "offset" => offset
|
|
72
|
-
).dig("data", "jobs")
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# Earliest funding-rate timestamp available for an exchange+symbol (ms, or nil).
|
|
76
|
-
# @param exchange [String]
|
|
77
|
-
# @param symbol [String]
|
|
78
|
-
def funding_range(exchange:, symbol:)
|
|
79
|
-
@http.get("/api/v1/private/backtest/funding-range",
|
|
80
|
-
"exchange" => exchange, "symbol" => symbol
|
|
81
|
-
)["earliest_ms"]
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
# Cancel an in-flight job.
|
|
85
|
-
# @param job_id [String]
|
|
86
|
-
def cancel(job_id)
|
|
87
|
-
@http.post("/api/v1/private/backtest/#{job_id}/cancel")
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
# Soft-delete a single job.
|
|
91
|
-
# @param job_id [String]
|
|
92
|
-
def delete(job_id)
|
|
93
|
-
@http.delete("/api/v1/private/backtest/#{job_id}")
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
# Soft-delete every non-favorited job. Returns a hash with "deleted" count.
|
|
97
|
-
def delete_all
|
|
98
|
-
@http.delete("/api/v1/private/backtest")
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Melaya
|
|
4
|
+
# Backtest API — run strategies against historical data on the Rust engine.
|
|
5
|
+
#
|
|
6
|
+
# Start a single run or a parameter sweep (grid / random), poll the job,
|
|
7
|
+
# then pull metrics, the equity curve, and the trade list. All backtests run
|
|
8
|
+
# natively on Melaya's in-house engine — no per-venue SDK in the loop.
|
|
9
|
+
#
|
|
10
|
+
# Maps to https://api.melaya.org/api/v1/private/backtest/* on the private plane.
|
|
11
|
+
class BacktestAPI
|
|
12
|
+
def initialize(http)
|
|
13
|
+
@http = http
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Start a backtest. +mode+ defaults to a single run; pass "grid_sweep" /
|
|
17
|
+
# "random_sweep" with +param_ranges+ to fan out a parameter search.
|
|
18
|
+
# Returns a hash with "job_id" (and optionally "count").
|
|
19
|
+
#
|
|
20
|
+
# @param body [Hash] see BacktestStart type in the reference SDKs
|
|
21
|
+
def start(body)
|
|
22
|
+
@http.post("/api/v1/private/backtest/start", body)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Job status + progress (+status+, +progress_pct+, ...).
|
|
26
|
+
# @param job_id [String]
|
|
27
|
+
def job(job_id)
|
|
28
|
+
@http.get("/api/v1/private/backtest/jobs/#{job_id}")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Metrics, equity curve, and OHLCV for a completed job.
|
|
32
|
+
# @param job_id [String]
|
|
33
|
+
def results(job_id)
|
|
34
|
+
@http.get("/api/v1/private/backtest/results/#{job_id}")["result"]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# The trade list for a completed job (default 500, max 5000 per call).
|
|
38
|
+
# @param job_id [String]
|
|
39
|
+
# @param limit [Integer, nil]
|
|
40
|
+
# @param offset [Integer, nil]
|
|
41
|
+
def trades(job_id, limit: nil, offset: nil)
|
|
42
|
+
@http.get("/api/v1/private/backtest/trades/#{job_id}",
|
|
43
|
+
"limit" => limit, "offset" => offset
|
|
44
|
+
)["trades"]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Ranked children of a sweep parent (default objective: sharpe DESC).
|
|
48
|
+
# @param parent_id [String]
|
|
49
|
+
# @param objective [String, nil]
|
|
50
|
+
# @param limit [Integer, nil]
|
|
51
|
+
def sweep(parent_id, objective: nil, limit: nil)
|
|
52
|
+
@http.get("/api/v1/private/backtest/sweep/#{parent_id}",
|
|
53
|
+
"objective" => objective, "limit" => limit
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Your backtest jobs, newest first.
|
|
58
|
+
# @param limit [Integer, nil]
|
|
59
|
+
# @param offset [Integer, nil]
|
|
60
|
+
def list(limit: nil, offset: nil)
|
|
61
|
+
@http.get("/api/v1/private/backtest",
|
|
62
|
+
"limit" => limit, "offset" => offset
|
|
63
|
+
).dig("data", "jobs")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Your favorited backtest jobs (Forge tier and above).
|
|
67
|
+
# @param limit [Integer, nil]
|
|
68
|
+
# @param offset [Integer, nil]
|
|
69
|
+
def favorites(limit: nil, offset: nil)
|
|
70
|
+
@http.get("/api/v1/private/backtest/favorites",
|
|
71
|
+
"limit" => limit, "offset" => offset
|
|
72
|
+
).dig("data", "jobs")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Earliest funding-rate timestamp available for an exchange+symbol (ms, or nil).
|
|
76
|
+
# @param exchange [String]
|
|
77
|
+
# @param symbol [String]
|
|
78
|
+
def funding_range(exchange:, symbol:)
|
|
79
|
+
@http.get("/api/v1/private/backtest/funding-range",
|
|
80
|
+
"exchange" => exchange, "symbol" => symbol
|
|
81
|
+
)["earliest_ms"]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Cancel an in-flight job.
|
|
85
|
+
# @param job_id [String]
|
|
86
|
+
def cancel(job_id)
|
|
87
|
+
@http.post("/api/v1/private/backtest/#{job_id}/cancel")
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Soft-delete a single job.
|
|
91
|
+
# @param job_id [String]
|
|
92
|
+
def delete(job_id)
|
|
93
|
+
@http.delete("/api/v1/private/backtest/#{job_id}")
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Soft-delete every non-favorited job. Returns a hash with "deleted" count.
|
|
97
|
+
def delete_all
|
|
98
|
+
@http.delete("/api/v1/private/backtest")
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# ── Parameter sweep optimizer (restRoutes.ts /api/v1/private/backtest/optimize) ──
|
|
102
|
+
|
|
103
|
+
# POST /api/v1/private/backtest/optimize
|
|
104
|
+
# Start a parameter sweep optimization (genetic/grid) over a strategy config.
|
|
105
|
+
# @param body [Hash] strategy config + param ranges + objective
|
|
106
|
+
def optimize_start(body)
|
|
107
|
+
@http.post("/api/v1/private/backtest/optimize", body)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# GET /api/v1/private/backtest/optimize
|
|
111
|
+
# List optimization sweep runs.
|
|
112
|
+
# @param params [Hash]
|
|
113
|
+
def optimize_list(params = {})
|
|
114
|
+
@http.get("/api/v1/private/backtest/optimize", params)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# GET /api/v1/private/backtest/optimize/:optRunId/status
|
|
118
|
+
# Get the status/progress of an optimization sweep run.
|
|
119
|
+
# @param opt_run_id [String]
|
|
120
|
+
def optimize_status(opt_run_id)
|
|
121
|
+
@http.get("/api/v1/private/backtest/optimize/#{URI.encode_www_form_component(opt_run_id.to_s)}/status")
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# POST /api/v1/private/backtest/optimize/:optRunId/cancel
|
|
125
|
+
# Cancel an in-progress optimization sweep.
|
|
126
|
+
# @param opt_run_id [String]
|
|
127
|
+
def optimize_cancel(opt_run_id)
|
|
128
|
+
@http.post("/api/v1/private/backtest/optimize/#{URI.encode_www_form_component(opt_run_id.to_s)}/cancel")
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# POST /api/v1/private/backtest/optimize/:optRunId/apply
|
|
132
|
+
# Apply best params from a completed optimization sweep to a strategy.
|
|
133
|
+
# @param opt_run_id [String]
|
|
134
|
+
# @param body [Hash]
|
|
135
|
+
def optimize_apply(opt_run_id, body = {})
|
|
136
|
+
@http.post("/api/v1/private/backtest/optimize/#{URI.encode_www_form_component(opt_run_id.to_s)}/apply", body)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Melaya
|
|
4
|
+
# Billing API — subscription status, Stripe checkout/portal sessions,
|
|
5
|
+
# public pricing plans, and credit balances.
|
|
6
|
+
#
|
|
7
|
+
# Maps to /api/v1/private/billing/* (authenticated) and
|
|
8
|
+
# /api/v1/billing/plans (public), and /api/v1/private/accounts/* for credits.
|
|
9
|
+
class BillingAPI
|
|
10
|
+
def initialize(http)
|
|
11
|
+
@http = http
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# GET /api/v1/private/billing/subscription
|
|
15
|
+
# Get caller's current Stripe subscription status and tier.
|
|
16
|
+
def subscription
|
|
17
|
+
@http.get("/api/v1/private/billing/subscription")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# POST /api/v1/private/billing/checkout
|
|
21
|
+
# Create Stripe Checkout session for tier upgrade.
|
|
22
|
+
# Returns a URL to redirect the user to.
|
|
23
|
+
# @param price_id [String, nil] Stripe price ID
|
|
24
|
+
# @param tier [String, nil]
|
|
25
|
+
def create_checkout(price_id: nil, tier: nil)
|
|
26
|
+
body = compact("priceId" => price_id, "tier" => tier)
|
|
27
|
+
@http.post("/api/v1/private/billing/checkout", body)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# POST /api/v1/private/billing/portal
|
|
31
|
+
# Create Stripe Customer Portal session for subscription management.
|
|
32
|
+
# Returns a URL to redirect the user to.
|
|
33
|
+
def create_portal
|
|
34
|
+
@http.post("/api/v1/private/billing/portal")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# GET /api/v1/billing/plans (public)
|
|
38
|
+
# Return public pricing plan details (price IDs for forge/bastion/citadel tiers).
|
|
39
|
+
def plans
|
|
40
|
+
@http.get("/api/v1/billing/plans")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# ── Credits ───────────────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
# GET /api/v1/private/accounts/credits
|
|
46
|
+
# Return current credit balance and transaction history.
|
|
47
|
+
def credits
|
|
48
|
+
@http.get("/api/v1/private/accounts/credits")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# GET /api/v1/private/accounts/credits/ai
|
|
52
|
+
# Return AI/LLM credit balance.
|
|
53
|
+
def ai_credits
|
|
54
|
+
@http.get("/api/v1/private/accounts/credits/ai")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# GET /api/v1/private/accounts/credits/portfolio-ideas
|
|
58
|
+
# Return portfolio-ideas feature credit balance.
|
|
59
|
+
def portfolio_ideas_credits
|
|
60
|
+
@http.get("/api/v1/private/accounts/credits/portfolio-ideas")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# GET /api/v1/private/accounts/credits/risk-monitoring
|
|
64
|
+
# Return risk-monitoring feature credit balance.
|
|
65
|
+
def risk_monitoring_credits
|
|
66
|
+
@http.get("/api/v1/private/accounts/credits/risk-monitoring")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def compact(hash)
|
|
72
|
+
hash.reject { |_, v| v.nil? }
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
data/lib/melaya/bugs.rb
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Melaya
|
|
4
|
+
# Bugs API — submit and track bug reports.
|
|
5
|
+
#
|
|
6
|
+
# Maps to /api/v1/private/bugs/*.
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
# melaya.bugs.create(title: "UI crash", description: "...")
|
|
10
|
+
# reports = melaya.bugs.list_mine
|
|
11
|
+
class BugsAPI
|
|
12
|
+
def initialize(http)
|
|
13
|
+
@http = http
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# POST /api/v1/private/bugs
|
|
17
|
+
# Submit a bug report (user-facing feedback form).
|
|
18
|
+
# @param title [String]
|
|
19
|
+
# @param description [String]
|
|
20
|
+
# @param extra [Hash] any additional fields
|
|
21
|
+
def create(title:, description: nil, **extra)
|
|
22
|
+
body = extra.transform_keys(&:to_s)
|
|
23
|
+
body["title"] = title
|
|
24
|
+
body["description"] = description unless description.nil?
|
|
25
|
+
@http.post("/api/v1/private/bugs", body)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# GET /api/v1/private/bugs/mine
|
|
29
|
+
# List bug reports submitted by the caller.
|
|
30
|
+
def list_mine
|
|
31
|
+
@http.get("/api/v1/private/bugs/mine")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# GET /api/v1/private/bugs/:bugId
|
|
35
|
+
# Get a single bug report by ID.
|
|
36
|
+
# @param bug_id [String]
|
|
37
|
+
def get(bug_id)
|
|
38
|
+
@http.get("/api/v1/private/bugs/#{enc(bug_id)}")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# POST /api/v1/private/bugs/:bugId/comments
|
|
42
|
+
# Add a comment to a bug report.
|
|
43
|
+
# @param bug_id [String]
|
|
44
|
+
# @param comment [String]
|
|
45
|
+
def add_comment(bug_id, comment:)
|
|
46
|
+
@http.post("/api/v1/private/bugs/#{enc(bug_id)}/comments", "comment" => comment)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# GET /api/v1/private/bugs/notifications
|
|
50
|
+
# List unread bug-related notifications for the caller.
|
|
51
|
+
def list_notifications
|
|
52
|
+
@http.get("/api/v1/private/bugs/notifications")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# POST /api/v1/private/bugs/notifications/read
|
|
56
|
+
# Mark bug notifications as read.
|
|
57
|
+
def mark_notifications_read(body = {})
|
|
58
|
+
@http.post("/api/v1/private/bugs/notifications/read", body)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def enc(s)
|
|
64
|
+
URI.encode_www_form_component(s.to_s)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Melaya
|
|
4
|
+
# Project Connectors API — manage credentials at project scope.
|
|
5
|
+
#
|
|
6
|
+
# Project-scoped connectors are isolated per project, letting separate
|
|
7
|
+
# projects use different API keys for the same service.
|
|
8
|
+
#
|
|
9
|
+
# Maps to /api/v1/private/projects/:project/connectors/*.
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# melaya.connectors.set("my-project", "openai", value: "sk-...")
|
|
13
|
+
# services = melaya.connectors.connected_services("my-project")
|
|
14
|
+
class ConnectorsAPI
|
|
15
|
+
def initialize(http)
|
|
16
|
+
@http = http
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# GET /api/v1/private/projects/:project/connectors/services
|
|
20
|
+
# List connected services for a project.
|
|
21
|
+
# @param project [String] project name
|
|
22
|
+
def connected_services(project)
|
|
23
|
+
@http.get("/api/v1/private/projects/#{enc(project)}/connectors/services")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# PUT /api/v1/private/projects/:project/connectors/:service
|
|
27
|
+
# Store a connector credential at project scope.
|
|
28
|
+
# @param project [String]
|
|
29
|
+
# @param service [String]
|
|
30
|
+
# @param value [String]
|
|
31
|
+
# @param key [String, nil]
|
|
32
|
+
# @param label [String, nil]
|
|
33
|
+
def set(project, service, value:, key: nil, label: nil)
|
|
34
|
+
body = compact("value" => value, "key" => key, "label" => label)
|
|
35
|
+
@http.put("/api/v1/private/projects/#{enc(project)}/connectors/#{enc(service)}", body)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# DELETE /api/v1/private/projects/:project/connectors/:service
|
|
39
|
+
# Delete a project-scoped connector credential.
|
|
40
|
+
# @param project [String]
|
|
41
|
+
# @param service [String]
|
|
42
|
+
def delete(project, service)
|
|
43
|
+
@http.delete("/api/v1/private/projects/#{enc(project)}/connectors/#{enc(service)}")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# POST /api/v1/private/projects/:project/connectors/env-handle
|
|
47
|
+
# Get a short-lived env-handle token for project-scoped credentials.
|
|
48
|
+
# @param project [String]
|
|
49
|
+
def env_handle(project)
|
|
50
|
+
@http.post("/api/v1/private/projects/#{enc(project)}/connectors/env-handle")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# POST /api/v1/private/projects/:project/connectors/google/oauth
|
|
54
|
+
# Start Google OAuth flow for project-scoped connector.
|
|
55
|
+
# @param project [String]
|
|
56
|
+
# @param body [Hash]
|
|
57
|
+
def google_oauth_start(project, body = {})
|
|
58
|
+
@http.post("/api/v1/private/projects/#{enc(project)}/connectors/google/oauth", body)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def enc(s)
|
|
64
|
+
URI.encode_www_form_component(s.to_s)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def compact(hash)
|
|
68
|
+
hash.reject { |_, v| v.nil? }
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|