mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-22 03:37:36 +08:00
Compare commits
2 Commits
eabd279d10
...
stable
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39eac5aca7 | ||
|
|
0f3e71560c |
@@ -262,15 +262,14 @@ func (s *Server) createDefaultStrategies(userID string, lang string) error {
|
|||||||
c.CoinSource.VergexLimit = 10
|
c.CoinSource.VergexLimit = 10
|
||||||
c.CoinSource.VergexMarketType = "all"
|
c.CoinSource.VergexMarketType = "all"
|
||||||
c.CoinSource.VergexChain = "hyperliquid"
|
c.CoinSource.VergexChain = "hyperliquid"
|
||||||
c.RiskControl.MaxPositions = 4
|
c.RiskControl.MaxPositions = 2
|
||||||
c.RiskControl.BTCETHMaxLeverage = 20
|
c.RiskControl.BTCETHMaxLeverage = 5
|
||||||
c.RiskControl.AltcoinMaxLeverage = 20
|
c.RiskControl.AltcoinMaxLeverage = 5
|
||||||
// 5× equity notional per position: 4 positions = 20x total account
|
// Few, concentrated positions held for big moves. 5x leverage so a
|
||||||
// notional (full margin, ~5% liquidation cushion). Aggressive by
|
// wide (-5%) stop is survivable rather than an instant liquidation;
|
||||||
// operator choice — bigger single positions; the 0.4 short-signal
|
// 2 positions × 2.5x = 5x total notional (full margin, ~20% cushion).
|
||||||
// floor keeps the book balanced so it is not a one-directional bet.
|
c.RiskControl.BTCETHMaxPositionValueRatio = 2.5
|
||||||
c.RiskControl.BTCETHMaxPositionValueRatio = 5.0
|
c.RiskControl.AltcoinMaxPositionValueRatio = 2.5
|
||||||
c.RiskControl.AltcoinMaxPositionValueRatio = 5.0
|
|
||||||
c.RiskControl.MaxMarginUsage = 1.0
|
c.RiskControl.MaxMarginUsage = 1.0
|
||||||
c.RiskControl.MinConfidence = 78
|
c.RiskControl.MinConfidence = 78
|
||||||
c.RiskControl.MinRiskRewardRatio = 3.0
|
c.RiskControl.MinRiskRewardRatio = 3.0
|
||||||
|
|||||||
@@ -54,16 +54,16 @@ func TestCreateDefaultStrategiesUsesOneReadyToRunClaw402Preset(t *testing.T) {
|
|||||||
if trendCfg.CoinSource.SourceType != "vergex_signal" || trendCfg.CoinSource.VergexLimit != 10 || trendCfg.CoinSource.VergexMarketType != "all" {
|
if trendCfg.CoinSource.SourceType != "vergex_signal" || trendCfg.CoinSource.VergexLimit != 10 || trendCfg.CoinSource.VergexMarketType != "all" {
|
||||||
t.Fatalf("default strategy should use the Claw402/Vergex all-market signal ranking, got %+v", trendCfg.CoinSource)
|
t.Fatalf("default strategy should use the Claw402/Vergex all-market signal ranking, got %+v", trendCfg.CoinSource)
|
||||||
}
|
}
|
||||||
if trendCfg.CoinSource.UseAI500 || trendCfg.RiskControl.MaxPositions != 4 {
|
if trendCfg.CoinSource.UseAI500 || trendCfg.RiskControl.MaxPositions != 2 {
|
||||||
t.Fatalf("default strategy should be Claw402/Vergex native with a 4-position balanced book, got coin=%+v risk=%+v", trendCfg.CoinSource, trendCfg.RiskControl)
|
t.Fatalf("default strategy should be Claw402/Vergex native with a 2-position concentrated book, got coin=%+v risk=%+v", trendCfg.CoinSource, trendCfg.RiskControl)
|
||||||
}
|
}
|
||||||
if trendCfg.RiskControl.BTCETHMaxLeverage != 20 || trendCfg.RiskControl.AltcoinMaxLeverage != 20 {
|
if trendCfg.RiskControl.BTCETHMaxLeverage != 5 || trendCfg.RiskControl.AltcoinMaxLeverage != 5 {
|
||||||
t.Fatalf("default strategy should use 20x leverage for all Claw402 opens, got risk=%+v", trendCfg.RiskControl)
|
t.Fatalf("default strategy should use 5x leverage for all Claw402 opens, got risk=%+v", trendCfg.RiskControl)
|
||||||
}
|
}
|
||||||
if trendCfg.RiskControl.BTCETHMaxPositionValueRatio != 5 ||
|
if trendCfg.RiskControl.BTCETHMaxPositionValueRatio != 2.5 ||
|
||||||
trendCfg.RiskControl.AltcoinMaxPositionValueRatio != 5 ||
|
trendCfg.RiskControl.AltcoinMaxPositionValueRatio != 2.5 ||
|
||||||
trendCfg.RiskControl.MaxMarginUsage != 1.0 {
|
trendCfg.RiskControl.MaxMarginUsage != 1.0 {
|
||||||
t.Fatalf("default strategy should size Claw402 opens at 5x equity notional (4 positions = 20x total at 20x), got risk=%+v", trendCfg.RiskControl)
|
t.Fatalf("default strategy should size Claw402 opens at 2.5x equity notional (2 positions = 5x total at 5x), got risk=%+v", trendCfg.RiskControl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -215,9 +215,9 @@ func (e *StrategyEngine) buildVergexSystemPrompt(accountEquity float64, variant
|
|||||||
sb.WriteString("- Ranking alone is not an entry reason; it only defines the candidate pool.\n")
|
sb.WriteString("- Ranking alone is not an entry reason; it only defines the candidate pool.\n")
|
||||||
sb.WriteString("- Every symbol in Candidate Coins is part of the allowed trading universe; missing detail can lower confidence or trigger waiting, but does not make the symbol non-tradable.\n")
|
sb.WriteString("- Every symbol in Candidate Coins is part of the allowed trading universe; missing detail can lower confidence or trigger waiting, but does not make the symbol non-tradable.\n")
|
||||||
sb.WriteString("- If Signal Lab or heatmap is absent from that symbol's Vergex Claw402 Signals, state it in reasoning; if it is present, never claim the symbol lacks that data.\n")
|
sb.WriteString("- If Signal Lab or heatmap is absent from that symbol's Vergex Claw402 Signals, state it in reasoning; if it is present, never claim the symbol lacks that data.\n")
|
||||||
sb.WriteString("- Avoid churn: unless stopping out or taking a strong profit, hold new positions for at least 60 minutes; avoid flat/noise closes until roughly 90 minutes; after closing a symbol, wait 90 minutes before re-entry; open at most 1 new position per hour.\n")
|
sb.WriteString("- Hold for BIG moves, do not churn: hold new positions for at least 4 hours; never close inside the -4%% to +6%% noise band before ~8 hours; after closing a symbol wait 3 hours before re-entry; open at most 1-2 new positions per hour. Small in-and-out trades bled this account to death on fees.\n")
|
||||||
sb.WriteString("- Fees are the main edge killer: a round trip costs roughly 0.1%% of notional (about 1%% of margin at 10x). Only take setups whose expected move to target is at least 3x that cost; fewer, higher-conviction, longer-hold trades beat frequent scalps.\n")
|
sb.WriteString("- Fees are the main edge killer: a round trip costs ~0.1%% of notional. Only enter setups where the realistic target is a LARGE move: stop-loss around -5%% and take-profit around +10-12%% (roughly 2:1 or better). Do not aim for 0.5%% scalps — they cannot cover fees. Fewer, high-conviction, wide-target, multi-hour holds only.\n")
|
||||||
sb.WriteString("- Stops must sit beyond invalidation; targets should prefer heatmap resistance/liquidation zones or valid risk/reward levels.\n\n")
|
sb.WriteString("- Set WIDE stops and targets: place the stop well beyond short-term noise (around -5%%) and the target at a distant heatmap resistance/liquidation zone (around +10-12%%). Give the position room to develop; do not exit on small green or small red.\n\n")
|
||||||
} else {
|
} else {
|
||||||
sb.WriteString("# You are the NOFX Claw402 auto-trader\n\n")
|
sb.WriteString("# You are the NOFX Claw402 auto-trader\n\n")
|
||||||
sb.WriteString("Trade only Hyperliquid instruments returned by this cycle's Claw402.ai/Vergex board. You may trade only the current candidate symbols and existing positions; never invent tickers or rotate outside the provided universe.\n\n")
|
sb.WriteString("Trade only Hyperliquid instruments returned by this cycle's Claw402.ai/Vergex board. You may trade only the current candidate symbols and existing positions; never invent tickers or rotate outside the provided universe.\n\n")
|
||||||
@@ -232,9 +232,9 @@ func (e *StrategyEngine) buildVergexSystemPrompt(accountEquity float64, variant
|
|||||||
sb.WriteString("- Ranking alone is not an entry reason; it only defines the candidate pool.\n")
|
sb.WriteString("- Ranking alone is not an entry reason; it only defines the candidate pool.\n")
|
||||||
sb.WriteString("- Every symbol in Candidate Coins is part of the allowed trading universe; missing detail can lower confidence or trigger waiting, but does not make the symbol non-tradable.\n")
|
sb.WriteString("- Every symbol in Candidate Coins is part of the allowed trading universe; missing detail can lower confidence or trigger waiting, but does not make the symbol non-tradable.\n")
|
||||||
sb.WriteString("- If Signal Lab or heatmap is absent from that symbol's Vergex Claw402 Signals, state it in reasoning; if it is present, never claim the symbol lacks that data.\n")
|
sb.WriteString("- If Signal Lab or heatmap is absent from that symbol's Vergex Claw402 Signals, state it in reasoning; if it is present, never claim the symbol lacks that data.\n")
|
||||||
sb.WriteString("- Avoid churn: unless stopping out or taking a strong profit, hold new positions for at least 60 minutes; avoid flat/noise closes until roughly 90 minutes; after closing a symbol, wait 90 minutes before re-entry; open at most 1 new position per hour.\n")
|
sb.WriteString("- Hold for BIG moves, do not churn: hold new positions for at least 4 hours; never close inside the -4%% to +6%% noise band before ~8 hours; after closing a symbol wait 3 hours before re-entry; open at most 1-2 new positions per hour. Small in-and-out trades bled this account to death on fees.\n")
|
||||||
sb.WriteString("- Fees are the main edge killer: a round trip costs roughly 0.1%% of notional (about 1%% of margin at 10x). Only take setups whose expected move to target is at least 3x that cost; fewer, higher-conviction, longer-hold trades beat frequent scalps.\n")
|
sb.WriteString("- Fees are the main edge killer: a round trip costs ~0.1%% of notional. Only enter setups where the realistic target is a LARGE move: stop-loss around -5%% and take-profit around +10-12%% (roughly 2:1 or better). Do not aim for 0.5%% scalps — they cannot cover fees. Fewer, high-conviction, wide-target, multi-hour holds only.\n")
|
||||||
sb.WriteString("- Stops must sit beyond invalidation; targets should prefer heatmap resistance/liquidation zones or valid risk/reward levels.\n\n")
|
sb.WriteString("- Set WIDE stops and targets: place the stop well beyond short-term noise (around -5%%) and the target at a distant heatmap resistance/liquidation zone (around +10-12%%). Give the position room to develop; do not exit on small green or small red.\n\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
writeModeVariant(&sb, variant, zh)
|
writeModeVariant(&sb, variant, zh)
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ func TestBuildSystemPromptUsesVergexClaw402Prompt(t *testing.T) {
|
|||||||
if !strings.Contains(prompt, "Direction must be data-driven") {
|
if !strings.Contains(prompt, "Direction must be data-driven") {
|
||||||
t.Fatalf("prompt should explain that direction is data-driven, not long-only:\n%s", prompt)
|
t.Fatalf("prompt should explain that direction is data-driven, not long-only:\n%s", prompt)
|
||||||
}
|
}
|
||||||
if !strings.Contains(prompt, "every open position must use exactly 20x") {
|
if !strings.Contains(prompt, "every open position must use exactly 5x") {
|
||||||
t.Fatalf("prompt should force 20x leverage for Claw402 opens:\n%s", prompt)
|
t.Fatalf("prompt should force 5x leverage for Claw402 opens:\n%s", prompt)
|
||||||
}
|
}
|
||||||
if !strings.Contains(prompt, "use the full max notional per position") {
|
if !strings.Contains(prompt, "use the full max notional per position") {
|
||||||
t.Fatalf("prompt should force full-size Claw402 opens:\n%s", prompt)
|
t.Fatalf("prompt should force full-size Claw402 opens:\n%s", prompt)
|
||||||
|
|||||||
@@ -1014,11 +1014,11 @@ func GetDefaultStrategyConfig(lang string) StrategyConfig {
|
|||||||
PriceRankingLimit: 10,
|
PriceRankingLimit: 10,
|
||||||
},
|
},
|
||||||
RiskControl: RiskControlConfig{
|
RiskControl: RiskControlConfig{
|
||||||
MaxPositions: 4, // Room for ~2 long + 2 short (CODE ENFORCED)
|
MaxPositions: 2, // Few, concentrated positions held for big moves (CODE ENFORCED)
|
||||||
BTCETHMaxLeverage: 20, // BTC/ETH exchange leverage (AI guided)
|
BTCETHMaxLeverage: 5, // Low leverage so a big (-5%) stop is survivable, not an instant liquidation
|
||||||
AltcoinMaxLeverage: 20, // TradeFi exchange leverage (AI guided)
|
AltcoinMaxLeverage: 5, // Low leverage so a big (-5%) stop is survivable, not an instant liquidation
|
||||||
BTCETHMaxPositionValueRatio: 5.0, // Per-position notional = equity × 5; 4 positions = 20x total (full margin, ~5% liquidation cushion — aggressive by operator choice)
|
BTCETHMaxPositionValueRatio: 2.5, // Per-position notional = equity × 2.5; 2 positions = 5x total (full margin at 5x, ~20% liquidation cushion)
|
||||||
AltcoinMaxPositionValueRatio: 5.0, // Per-position notional = equity × 5; 4 positions = 20x total (full margin, ~5% liquidation cushion — aggressive by operator choice)
|
AltcoinMaxPositionValueRatio: 2.5, // Per-position notional = equity × 2.5; 2 positions = 5x total (full margin at 5x, ~20% liquidation cushion)
|
||||||
MaxMarginUsage: 1.0, // Claw402 Autopilot intentionally uses full margin when opening
|
MaxMarginUsage: 1.0, // Claw402 Autopilot intentionally uses full margin when opening
|
||||||
MinPositionSize: 12, // Min 12 USDT per position (CODE ENFORCED)
|
MinPositionSize: 12, // Min 12 USDT per position (CODE ENFORCED)
|
||||||
MinRiskRewardRatio: 3.0, // Min 3:1 profit/loss ratio (AI guided)
|
MinRiskRewardRatio: 3.0, // Min 3:1 profit/loss ratio (AI guided)
|
||||||
|
|||||||
@@ -10,23 +10,25 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Live history: trades held under an hour were net-negative after fees
|
// "Hold for big moves, don't churn" regime. Live history showed the
|
||||||
// (the 15-60m bucket bled), while the edge concentrated in 1h+ holds.
|
// account bleeding to death by fees: 0.3-0.5% in/out moves where a ~0.14%
|
||||||
autopilotMinHoldDuration = 60 * time.Minute
|
// round-trip fee ate 30-50% of every small winner. These values force
|
||||||
autopilotNoiseCloseHoldDuration = 90 * time.Minute
|
// positions to be held for hours and to develop meaningful moves before
|
||||||
autopilotReentryCooldown = 30 * time.Minute
|
// closing, and cut the trade frequency hard.
|
||||||
// Allow one long + one short per cycle. The real exposure/churn limits are
|
autopilotMinHoldDuration = 4 * time.Hour
|
||||||
// MaxPositions (concurrent) + the 45m min-hold + the 90m per-symbol reentry
|
autopilotNoiseCloseHoldDuration = 8 * time.Hour
|
||||||
// cooldown, so the per-hour cap only needs to be high enough not to block the
|
autopilotReentryCooldown = 3 * time.Hour
|
||||||
// directional pair from re-establishing after positions close. A tight value
|
// Drastically cut churn: at most a couple of new positions per hour/cycle.
|
||||||
// here (e.g. 2) starves the strategy: once a couple opens fire, every later
|
autopilotMaxOpensPerHour = 3
|
||||||
// cycle is blocked and the book drains to flat. Keep it generous.
|
autopilotMaxOpensPerCycle = 2
|
||||||
autopilotMaxOpensPerHour = 30
|
// Wide, asymmetric exits. Cut a loser only at a real -5% (at 5x leverage
|
||||||
autopilotMaxOpensPerCycle = 6
|
// that is -25% of margin — survivable), let a winner run to +12% before
|
||||||
earlyCloseStopLossBypassPct = -2.5
|
// any early take-profit. The noise band (-4%..+6%) blocks closing on the
|
||||||
earlyCloseTakeProfitBypassPct = 5.0
|
// small moves that were grinding the account to nothing.
|
||||||
noiseCloseLossFloorPct = -1.0
|
earlyCloseStopLossBypassPct = -5.0
|
||||||
noiseCloseProfitCeilingPct = 2.0
|
earlyCloseTakeProfitBypassPct = 12.0
|
||||||
|
noiseCloseLossFloorPct = -4.0
|
||||||
|
noiseCloseProfitCeilingPct = 6.0
|
||||||
)
|
)
|
||||||
|
|
||||||
func isOpenAction(action string) bool {
|
func isOpenAction(action string) bool {
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ func TestTradeThrottleBlocksEarlyNoiseClose(t *testing.T) {
|
|||||||
|
|
||||||
func TestTradeThrottleAllowsEarlyHardStop(t *testing.T) {
|
func TestTradeThrottleAllowsEarlyHardStop(t *testing.T) {
|
||||||
at := &AutoTrader{}
|
at := &AutoTrader{}
|
||||||
ctx := throttleContext("xyz:INTC", "long", 20*time.Minute, -3.0)
|
// Only a real -5% stop bypasses the min hold now.
|
||||||
|
ctx := throttleContext("xyz:INTC", "long", 20*time.Minute, -6.0)
|
||||||
|
|
||||||
reason := at.tradeThrottleReason(kernel.Decision{Symbol: "xyz:INTC", Action: "close_long"}, ctx, 0)
|
reason := at.tradeThrottleReason(kernel.Decision{Symbol: "xyz:INTC", Action: "close_long"}, ctx, 0)
|
||||||
if reason != "" {
|
if reason != "" {
|
||||||
@@ -42,7 +43,8 @@ func TestTradeThrottleAllowsEarlyHardStop(t *testing.T) {
|
|||||||
|
|
||||||
func TestTradeThrottleBlocksFlatCloseInsideNoiseWindow(t *testing.T) {
|
func TestTradeThrottleBlocksFlatCloseInsideNoiseWindow(t *testing.T) {
|
||||||
at := &AutoTrader{}
|
at := &AutoTrader{}
|
||||||
ctx := throttleContext("xyz:INTC", "long", 60*time.Minute, 0.4)
|
// Held past the 4h min hold but still inside the wide -4%..+6% noise band.
|
||||||
|
ctx := throttleContext("xyz:INTC", "long", 5*time.Hour, 0.4)
|
||||||
|
|
||||||
reason := at.tradeThrottleReason(kernel.Decision{Symbol: "xyz:INTC", Action: "close_long"}, ctx, 0)
|
reason := at.tradeThrottleReason(kernel.Decision{Symbol: "xyz:INTC", Action: "close_long"}, ctx, 0)
|
||||||
if !strings.Contains(reason, "noise band") {
|
if !strings.Contains(reason, "noise band") {
|
||||||
@@ -52,7 +54,8 @@ func TestTradeThrottleBlocksFlatCloseInsideNoiseWindow(t *testing.T) {
|
|||||||
|
|
||||||
func TestTradeThrottleAllowsConfirmedLossAfterMinimumHold(t *testing.T) {
|
func TestTradeThrottleAllowsConfirmedLossAfterMinimumHold(t *testing.T) {
|
||||||
at := &AutoTrader{}
|
at := &AutoTrader{}
|
||||||
ctx := throttleContext("xyz:INTC", "long", 60*time.Minute, -1.2)
|
// Past the 4h min hold, loss beyond the -4% noise floor → close allowed.
|
||||||
|
ctx := throttleContext("xyz:INTC", "long", 5*time.Hour, -4.5)
|
||||||
|
|
||||||
reason := at.tradeThrottleReason(kernel.Decision{Symbol: "xyz:INTC", Action: "close_long"}, ctx, 0)
|
reason := at.tradeThrottleReason(kernel.Decision{Symbol: "xyz:INTC", Action: "close_long"}, ctx, 0)
|
||||||
if reason != "" {
|
if reason != "" {
|
||||||
@@ -76,13 +79,13 @@ func TestTradeThrottleBlocksOpensOverCycleCap(t *testing.T) {
|
|||||||
at := &AutoTrader{}
|
at := &AutoTrader{}
|
||||||
ctx := &kernel.Context{}
|
ctx := &kernel.Context{}
|
||||||
|
|
||||||
// under the 6-per-cycle cap, a further open is allowed
|
// under the 2-per-cycle cap, a further open is allowed
|
||||||
if reason := at.tradeThrottleReason(kernel.Decision{Symbol: "xyz:INTC", Action: "open_long"}, ctx, 5); reason != "" {
|
if reason := at.tradeThrottleReason(kernel.Decision{Symbol: "xyz:INTC", Action: "open_long"}, ctx, 1); reason != "" {
|
||||||
t.Fatalf("expected open within the 6-per-cycle cap to be allowed, got %q", reason)
|
t.Fatalf("expected open within the 2-per-cycle cap to be allowed, got %q", reason)
|
||||||
}
|
}
|
||||||
// at the cap, the next open is blocked
|
// at the cap, the next open is blocked
|
||||||
if reason := at.tradeThrottleReason(kernel.Decision{Symbol: "xyz:INTC", Action: "open_long"}, ctx, 6); !strings.Contains(reason, "6 new position") {
|
if reason := at.tradeThrottleReason(kernel.Decision{Symbol: "xyz:INTC", Action: "open_long"}, ctx, 2); !strings.Contains(reason, "2 new position") {
|
||||||
t.Fatalf("expected open beyond the 6-per-cycle cap to be blocked, got %q", reason)
|
t.Fatalf("expected open beyond the 2-per-cycle cap to be blocked, got %q", reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,13 +20,16 @@ func (t *HyperliquidTrader) SyncOrdersFromHyperliquid(traderID string, exchangeI
|
|||||||
return fmt.Errorf("store is nil")
|
return fmt.Errorf("store is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get recent trades (last 24 hours)
|
// Look back 7 days. GetTrades now pulls up to 2000 recent fills (UserFills)
|
||||||
startTime := time.Now().Add(-24 * time.Hour)
|
// and filters to this window, so a wide lookback backfills any fills missed
|
||||||
|
// during past outages/gaps without dropping recent ones. Dedup by trade ID
|
||||||
|
// keeps re-processing idempotent.
|
||||||
|
startTime := time.Now().Add(-7 * 24 * time.Hour)
|
||||||
|
|
||||||
logger.Infof("🔄 Syncing Hyperliquid trades from: %s", startTime.Format(time.RFC3339))
|
logger.Infof("🔄 Syncing Hyperliquid trades from: %s", startTime.Format(time.RFC3339))
|
||||||
|
|
||||||
// Use GetTrades method to fetch trade records
|
// Use GetTrades method to fetch trade records
|
||||||
trades, err := t.GetTrades(startTime, 1000)
|
trades, err := t.GetTrades(startTime, 2000)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get trades: %w", err)
|
return fmt.Errorf("failed to get trades: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
hl "github.com/sonirico/go-hyperliquid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetBalance gets account balance
|
// GetBalance gets account balance
|
||||||
@@ -548,15 +550,23 @@ func (t *HyperliquidTrader) GetClosedPnL(startTime time.Time, limit int) ([]type
|
|||||||
|
|
||||||
// GetTrades retrieves trade history from Hyperliquid
|
// GetTrades retrieves trade history from Hyperliquid
|
||||||
func (t *HyperliquidTrader) GetTrades(startTime time.Time, limit int) ([]types.TradeRecord, error) {
|
func (t *HyperliquidTrader) GetTrades(startTime time.Time, limit int) ([]types.TradeRecord, error) {
|
||||||
// Use UserFillsByTime API
|
// Use UserFills (returns up to 2000 recent fills) rather than
|
||||||
|
// UserFillsByTime, which is hard-capped at 100 fills per response. At
|
||||||
|
// this trading frequency the account exceeds 100 fills/24h, so
|
||||||
|
// UserFillsByTime silently dropped ~20% of fills — skewing recorded PnL
|
||||||
|
// and fees away from the exchange truth. 2000 recent fills covers many
|
||||||
|
// days of history; we filter to startTime client-side.
|
||||||
startTimeMs := startTime.UnixMilli()
|
startTimeMs := startTime.UnixMilli()
|
||||||
fills, err := t.exchange.Info().UserFillsByTime(t.ctx, t.walletAddr, startTimeMs, nil, nil)
|
fills, err := t.exchange.Info().UserFills(t.ctx, hl.UserFillsParams{Address: t.walletAddr})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get user fills: %w", err)
|
return nil, fmt.Errorf("failed to get user fills: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var trades []types.TradeRecord
|
var trades []types.TradeRecord
|
||||||
for _, fill := range fills {
|
for _, fill := range fills {
|
||||||
|
if fill.Time < startTimeMs {
|
||||||
|
continue
|
||||||
|
}
|
||||||
price, _ := strconv.ParseFloat(fill.Price, 64)
|
price, _ := strconv.ParseFloat(fill.Price, 64)
|
||||||
qty, _ := strconv.ParseFloat(fill.Size, 64)
|
qty, _ := strconv.ParseFloat(fill.Size, 64)
|
||||||
fee, _ := strconv.ParseFloat(fill.Fee, 64)
|
fee, _ := strconv.ParseFloat(fill.Fee, 64)
|
||||||
|
|||||||
@@ -1388,14 +1388,13 @@ export function StrategyStudioPage() {
|
|||||||
}),
|
}),
|
||||||
risk_control: defaultRisk({
|
risk_control: defaultRisk({
|
||||||
...base.ai_config?.risk_control,
|
...base.ai_config?.risk_control,
|
||||||
max_positions: 4,
|
max_positions: 2,
|
||||||
btc_eth_max_leverage: 20,
|
btc_eth_max_leverage: 5,
|
||||||
altcoin_max_leverage: 20,
|
altcoin_max_leverage: 5,
|
||||||
// 5× equity notional per position — 4 positions = 20x total account
|
// Few, concentrated positions held for big moves. 5x leverage so a
|
||||||
// notional (full margin, ~5% liquidation cushion). Aggressive by
|
// wide (-5%) stop is survivable; 2 positions × 2.5x = 5x total.
|
||||||
// operator choice; the 0.4 short-signal floor keeps the book balanced.
|
btc_eth_max_position_value_ratio: 2.5,
|
||||||
btc_eth_max_position_value_ratio: 5,
|
altcoin_max_position_value_ratio: 2.5,
|
||||||
altcoin_max_position_value_ratio: 5,
|
|
||||||
max_margin_usage: 1.0,
|
max_margin_usage: 1.0,
|
||||||
min_confidence: 78,
|
min_confidence: 78,
|
||||||
min_risk_reward_ratio: 3,
|
min_risk_reward_ratio: 3,
|
||||||
|
|||||||
Reference in New Issue
Block a user