Spaces:
Sleeping
Sleeping
Daniel Alnawaz commited on
fixing order issues
Browse files- hbot.py +20 -1
- strategy_mean_reversion_zscore.py +26 -22
- strategy_rsi_extreme.py +0 -97
- strategy_st_pullback.py +0 -120
hbot.py
CHANGED
|
@@ -252,6 +252,15 @@ class HyperliquidAPI:
|
|
| 252 |
logger.error(f"Size rounded to zero for {coin} (sz_dec={sz_dec}). Skipping.")
|
| 253 |
return None
|
| 254 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
limit_px = round(px * 1.02, px_dec)
|
| 256 |
logger.info(f"Placing {'BUY' if is_buy else 'SELL'} limit-IOC {sz} {coin} @ {limit_px} (mark={round(px, px_dec)}) [px_dec={px_dec} sz_dec={sz_dec}]")
|
| 257 |
|
|
@@ -372,7 +381,7 @@ class HyperliquidAPI:
|
|
| 372 |
Guarantees a fill but pays taker fee.
|
| 373 |
Best for fast strategies (scalp 15m/30m, hype).
|
| 374 |
|
| 375 |
-
|
| 376 |
"""
|
| 377 |
sz_dec = self.get_sz_decimals(coin)
|
| 378 |
px_dec = self.get_px_decimals(coin)
|
|
@@ -380,6 +389,16 @@ class HyperliquidAPI:
|
|
| 380 |
if sz <= 0:
|
| 381 |
logger.error(f"Size rounded to zero for {coin}. Skipping.")
|
| 382 |
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 383 |
limit_px = round(float(px), px_dec)
|
| 384 |
|
| 385 |
tif = "Alo" if post_only else "Gtc"
|
|
|
|
| 252 |
logger.error(f"Size rounded to zero for {coin} (sz_dec={sz_dec}). Skipping.")
|
| 253 |
return None
|
| 254 |
|
| 255 |
+
# ── Check notional after rounding ───────────────────────────────────
|
| 256 |
+
notional_after_rounding = sz * px
|
| 257 |
+
if notional_after_rounding < 10.0:
|
| 258 |
+
logger.error(
|
| 259 |
+
f"[{coin}] Notional ${notional_after_rounding:.2f} falls below $10 after rounding "
|
| 260 |
+
f"(sz={sz}, px={px:.{px_dec}f}). Skipping."
|
| 261 |
+
)
|
| 262 |
+
return None
|
| 263 |
+
|
| 264 |
limit_px = round(px * 1.02, px_dec)
|
| 265 |
logger.info(f"Placing {'BUY' if is_buy else 'SELL'} limit-IOC {sz} {coin} @ {limit_px} (mark={round(px, px_dec)}) [px_dec={px_dec} sz_dec={sz_dec}]")
|
| 266 |
|
|
|
|
| 381 |
Guarantees a fill but pays taker fee.
|
| 382 |
Best for fast strategies (scalp 15m/30m, hype).
|
| 383 |
|
| 384 |
+
"none" — log the rejection and give up (original behaviour).
|
| 385 |
"""
|
| 386 |
sz_dec = self.get_sz_decimals(coin)
|
| 387 |
px_dec = self.get_px_decimals(coin)
|
|
|
|
| 389 |
if sz <= 0:
|
| 390 |
logger.error(f"Size rounded to zero for {coin}. Skipping.")
|
| 391 |
return None
|
| 392 |
+
|
| 393 |
+
# ── Check notional after rounding ───────────────────────────────────
|
| 394 |
+
notional_after_rounding = sz * px
|
| 395 |
+
if notional_after_rounding < 10.0:
|
| 396 |
+
logger.error(
|
| 397 |
+
f"[{coin}] Notional ${notional_after_rounding:.2f} falls below $10 after rounding "
|
| 398 |
+
f"(sz={sz}, px={px:.{px_dec}f}). Skipping."
|
| 399 |
+
)
|
| 400 |
+
return None
|
| 401 |
+
|
| 402 |
limit_px = round(float(px), px_dec)
|
| 403 |
|
| 404 |
tif = "Alo" if post_only else "Gtc"
|
strategy_mean_reversion_zscore.py
CHANGED
|
@@ -3,14 +3,15 @@
|
|
| 3 |
Strategy: Z-Score Mean Reversion (1h)
|
| 4 |
=====================================
|
| 5 |
This strategy identifies extreme price deviations from the mean (20-period SMA)
|
| 6 |
-
and trades the reversion back to that mean.
|
| 7 |
|
| 8 |
Core Logic:
|
| 9 |
- Z-Score: (Close - SMA) / StdDev. Measures how many standard deviations price is from the mean.
|
| 10 |
-
- Entry (Long): Z-Score < -
|
| 11 |
-
- Entry (Short): Z-Score >
|
| 12 |
- Exit: Price hits the SMA (Z-Score returns to 0) or Stop Loss is hit.
|
| 13 |
-
- Filter: ADX <
|
|
|
|
| 14 |
"""
|
| 15 |
|
| 16 |
from __future__ import annotations
|
|
@@ -59,24 +60,27 @@ class StrategyEngine:
|
|
| 59 |
return d.dropna()
|
| 60 |
|
| 61 |
def signal_for_row(self, row: pd.Series) -> int:
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
|
|
|
|
|
|
|
|
|
| 80 |
|
| 81 |
def risk_levels(self, entry: float, atr: float, side: int, atr_pct: float = 0.0, row: Optional[pd.Series] = None) -> Tuple[float, float]:
|
| 82 |
# TP is the mean (SMA), which we can approximate as the current entry minus/plus some distance
|
|
|
|
| 3 |
Strategy: Z-Score Mean Reversion (1h)
|
| 4 |
=====================================
|
| 5 |
This strategy identifies extreme price deviations from the mean (20-period SMA)
|
| 6 |
+
and trades the reversion back to that mean, with 200 EMA directional filter.
|
| 7 |
|
| 8 |
Core Logic:
|
| 9 |
- Z-Score: (Close - SMA) / StdDev. Measures how many standard deviations price is from the mean.
|
| 10 |
+
- Entry (Long): Z-Score < -1.5 (Oversold) AND Price > 200 EMA (reversion in uptrend).
|
| 11 |
+
- Entry (Short): Z-Score > 1.5 (Overbought) AND Price < 200 EMA (reversion in downtrend).
|
| 12 |
- Exit: Price hits the SMA (Z-Score returns to 0) or Stop Loss is hit.
|
| 13 |
+
- Filter: ADX < 30 (avoid strong trends), 200 EMA for trend direction.
|
| 14 |
+
- Philosophy: Trade extreme reversals only in the direction of the longer-term trend.
|
| 15 |
"""
|
| 16 |
|
| 17 |
from __future__ import annotations
|
|
|
|
| 60 |
return d.dropna()
|
| 61 |
|
| 62 |
def signal_for_row(self, row: pd.Series) -> int:
|
| 63 |
+
z = float(row["zscore"])
|
| 64 |
+
adx = float(row["adx"])
|
| 65 |
+
close = float(row["close"])
|
| 66 |
+
ema200 = float(row["ema200"])
|
| 67 |
+
|
| 68 |
+
# Only trade in moderately trending or ranging markets
|
| 69 |
+
if adx > 30:
|
| 70 |
+
return 0
|
| 71 |
+
|
| 72 |
+
# ── 200 EMA directional filter (only trade extreme reversion in direction of trend) ──
|
| 73 |
+
# LONG only if z-score is BELOW -1.5 AND price is ABOVE 200 EMA (reversion in uptrend)
|
| 74 |
+
# This avoids catching falling knives in downtrends
|
| 75 |
+
if z < -1.5 and close > ema200:
|
| 76 |
+
return 1
|
| 77 |
+
|
| 78 |
+
# SHORT only if z-score is ABOVE 1.5 AND price is BELOW 200 EMA (reversion in downtrend)
|
| 79 |
+
# This avoids catching rallies in uptrends
|
| 80 |
+
if z > 1.5 and close < ema200:
|
| 81 |
+
return -1
|
| 82 |
+
|
| 83 |
+
return 0
|
| 84 |
|
| 85 |
def risk_levels(self, entry: float, atr: float, side: int, atr_pct: float = 0.0, row: Optional[pd.Series] = None) -> Tuple[float, float]:
|
| 86 |
# TP is the mean (SMA), which we can approximate as the current entry minus/plus some distance
|
strategy_rsi_extreme.py
DELETED
|
@@ -1,97 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""
|
| 3 |
-
Strategy: RSI Extreme Reversal (1h)
|
| 4 |
-
====================================
|
| 5 |
-
Targets "over-extended" markets using multiple RSI timeframes and Bollinger Bands.
|
| 6 |
-
Designed to capture sharp reversals after parabolic moves.
|
| 7 |
-
|
| 8 |
-
Core Logic:
|
| 9 |
-
- RSI(14) and RSI(2) for dual-speed momentum assessment.
|
| 10 |
-
- Bollinger Bands (20, 2.5) to identify price extremes.
|
| 11 |
-
- Entry (Long): RSI(14) < 30 AND RSI(2) < 10 AND price below Lower BB.
|
| 12 |
-
- Entry (Short): RSI(14) > 70 AND RSI(2) > 90 AND price above Upper BB.
|
| 13 |
-
- Exit: RSI(14) crosses back to the mean (50) or 2:1 RR hit.
|
| 14 |
-
"""
|
| 15 |
-
|
| 16 |
-
from __future__ import annotations
|
| 17 |
-
from dataclasses import dataclass
|
| 18 |
-
from typing import Dict, Optional, Tuple
|
| 19 |
-
import numpy as np
|
| 20 |
-
import pandas as pd
|
| 21 |
-
import ta
|
| 22 |
-
|
| 23 |
-
@dataclass
|
| 24 |
-
class TradeIntent:
|
| 25 |
-
coin: str
|
| 26 |
-
timeframe: str
|
| 27 |
-
side: str
|
| 28 |
-
entry_price: float
|
| 29 |
-
stop_loss: float
|
| 30 |
-
take_profit: float
|
| 31 |
-
confidence: float
|
| 32 |
-
|
| 33 |
-
class StrategyEngine:
|
| 34 |
-
def __init__(self, config: Dict):
|
| 35 |
-
self.config = config
|
| 36 |
-
self.use_maker_orders = False
|
| 37 |
-
|
| 38 |
-
def add_indicators(self, df: pd.DataFrame) -> pd.DataFrame:
|
| 39 |
-
d = df.copy()
|
| 40 |
-
|
| 41 |
-
# RSI 14 and 2
|
| 42 |
-
d["rsi14"] = ta.momentum.RSIIndicator(d["close"], window=14).rsi()
|
| 43 |
-
d["rsi2"] = ta.momentum.RSIIndicator(d["close"], window=2).rsi()
|
| 44 |
-
|
| 45 |
-
# Bollinger Bands (20, 2.5)
|
| 46 |
-
bb = ta.volatility.BollingerBands(d["close"], window=20, window_dev=2.5)
|
| 47 |
-
d["bb_high"] = bb.bollinger_hband()
|
| 48 |
-
d["bb_low"] = bb.bollinger_lband()
|
| 49 |
-
|
| 50 |
-
# ATR for risk
|
| 51 |
-
d["atr"] = ta.volatility.AverageTrueRange(d["high"], d["low"], d["close"], window=14).average_true_range()
|
| 52 |
-
d["atr_pct"] = d["atr"] / d["close"] * 100
|
| 53 |
-
|
| 54 |
-
return d.dropna()
|
| 55 |
-
|
| 56 |
-
def signal_for_row(self, row: pd.Series) -> int:
|
| 57 |
-
close = float(row["close"])
|
| 58 |
-
rsi14 = float(row["rsi14"])
|
| 59 |
-
rsi2 = float(row["rsi2"])
|
| 60 |
-
bb_high = float(row["bb_high"])
|
| 61 |
-
bb_low = float(row["bb_low"])
|
| 62 |
-
|
| 63 |
-
# LONG: Deeply oversold on both RSIs + Price below BB Lower
|
| 64 |
-
if rsi14 < 30 and rsi2 < 10 and close < bb_low:
|
| 65 |
-
return 1
|
| 66 |
-
|
| 67 |
-
# SHORT: Deeply overbought on both RSIs + Price above BB Upper
|
| 68 |
-
if rsi14 > 70 and rsi2 > 90 and close > bb_high:
|
| 69 |
-
return -1
|
| 70 |
-
|
| 71 |
-
return 0
|
| 72 |
-
|
| 73 |
-
def risk_levels(self, entry: float, atr: float, side: int, atr_pct: float = 0.0, row: Optional[pd.Series] = None) -> Tuple[float, float]:
|
| 74 |
-
# Tight 1.5 ATR Stop, 3.5 ATR TP (Aggressive RR for sharp reversals)
|
| 75 |
-
if side == 1:
|
| 76 |
-
sl = entry - (1.5 * atr)
|
| 77 |
-
tp = entry + (3.5 * atr)
|
| 78 |
-
else:
|
| 79 |
-
sl = entry + (1.5 * atr)
|
| 80 |
-
tp = entry - (3.5 * atr)
|
| 81 |
-
return sl, tp
|
| 82 |
-
|
| 83 |
-
def trail_stop(self, side: int, entry: float, current_sl: float, close: float, atr: float) -> float:
|
| 84 |
-
# Trail more tightly once in profit
|
| 85 |
-
if side == 1:
|
| 86 |
-
return max(current_sl, close - (1.5 * atr))
|
| 87 |
-
else:
|
| 88 |
-
return min(current_sl, close + (1.5 * atr))
|
| 89 |
-
|
| 90 |
-
def build_intent(self, coin: str, timeframe: str, row: pd.Series) -> Optional[TradeIntent]:
|
| 91 |
-
signal = self.signal_for_row(row)
|
| 92 |
-
if signal == 0:
|
| 93 |
-
return None
|
| 94 |
-
side = "long" if signal == 1 else "short"
|
| 95 |
-
entry = float(row["close"])
|
| 96 |
-
stop, tp = self.risk_levels(entry, float(row["atr"]), signal, 0, row)
|
| 97 |
-
return TradeIntent(coin, timeframe, side, entry, stop, tp, 0.85)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
strategy_st_pullback.py
DELETED
|
@@ -1,120 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""
|
| 3 |
-
Strategy: SuperTrend Pullback (1h)
|
| 4 |
-
==================================
|
| 5 |
-
A trend-following strategy that enters on pullbacks during a confirmed SuperTrend.
|
| 6 |
-
Matches the logic style of 'strategy_hype' and 'strategy_conservative'.
|
| 7 |
-
|
| 8 |
-
Core Logic:
|
| 9 |
-
- SuperTrend (10, 3.0) for trend direction.
|
| 10 |
-
- RSI (14) for pullback timing.
|
| 11 |
-
- Entry (Long): SuperTrend is UP and RSI < 45 (Pullback in uptrend).
|
| 12 |
-
- Entry (Short): SuperTrend is DOWN and RSI > 55 (Bounce in downtrend).
|
| 13 |
-
- ATR-based stops and targets (1.5:3.0 RR).
|
| 14 |
-
"""
|
| 15 |
-
|
| 16 |
-
from __future__ import annotations
|
| 17 |
-
from dataclasses import dataclass
|
| 18 |
-
from typing import Dict, Optional, Tuple
|
| 19 |
-
import numpy as np
|
| 20 |
-
import pandas as pd
|
| 21 |
-
import ta
|
| 22 |
-
|
| 23 |
-
@dataclass
|
| 24 |
-
class TradeIntent:
|
| 25 |
-
coin: str
|
| 26 |
-
timeframe: str
|
| 27 |
-
side: str
|
| 28 |
-
entry_price: float
|
| 29 |
-
stop_loss: float
|
| 30 |
-
take_profit: float
|
| 31 |
-
confidence: float
|
| 32 |
-
|
| 33 |
-
class StrategyEngine:
|
| 34 |
-
def __init__(self, config: Dict):
|
| 35 |
-
self.config = config
|
| 36 |
-
self.use_maker_orders = False
|
| 37 |
-
|
| 38 |
-
def add_indicators(self, df: pd.DataFrame) -> pd.DataFrame:
|
| 39 |
-
d = df.copy()
|
| 40 |
-
|
| 41 |
-
# ATR for SuperTrend and Risk
|
| 42 |
-
d["atr"] = ta.volatility.AverageTrueRange(d["high"], d["low"], d["close"], window=14).average_true_range()
|
| 43 |
-
d["atr_pct"] = d["atr"] / d["close"] * 100
|
| 44 |
-
|
| 45 |
-
# RSI 14
|
| 46 |
-
d["rsi"] = ta.momentum.RSIIndicator(d["close"], window=14).rsi()
|
| 47 |
-
|
| 48 |
-
# SuperTrend (10, 3.0)
|
| 49 |
-
multiplier = 3.0
|
| 50 |
-
hl2 = (d["high"] + d["low"]) / 2
|
| 51 |
-
d["st_upper_basic"] = hl2 + (multiplier * d["atr"])
|
| 52 |
-
d["st_lower_basic"] = hl2 - (multiplier * d["atr"])
|
| 53 |
-
|
| 54 |
-
upper_band = d["st_upper_basic"].values
|
| 55 |
-
lower_band = d["st_lower_basic"].values
|
| 56 |
-
close = d["close"].values
|
| 57 |
-
|
| 58 |
-
final_upper = np.zeros(len(d))
|
| 59 |
-
final_lower = np.zeros(len(d))
|
| 60 |
-
direction = np.zeros(len(d))
|
| 61 |
-
|
| 62 |
-
for i in range(1, len(d)):
|
| 63 |
-
# Upper Band
|
| 64 |
-
if upper_band[i] < final_upper[i-1] or close[i-1] > final_upper[i-1]:
|
| 65 |
-
final_upper[i] = upper_band[i]
|
| 66 |
-
else:
|
| 67 |
-
final_upper[i] = final_upper[i-1]
|
| 68 |
-
# Lower Band
|
| 69 |
-
if lower_band[i] > final_lower[i-1] or close[i-1] < final_lower[i-1]:
|
| 70 |
-
final_lower[i] = lower_band[i]
|
| 71 |
-
else:
|
| 72 |
-
final_lower[i] = final_lower[i-1]
|
| 73 |
-
# Direction
|
| 74 |
-
if i == 1: direction[i] = 1
|
| 75 |
-
elif close[i] > final_upper[i-1]: direction[i] = 1
|
| 76 |
-
elif close[i] < final_lower[i-1]: direction[i] = -1
|
| 77 |
-
else: direction[i] = direction[i-1]
|
| 78 |
-
|
| 79 |
-
d["st_direction"] = direction
|
| 80 |
-
|
| 81 |
-
return d.dropna()
|
| 82 |
-
|
| 83 |
-
def signal_for_row(self, row: pd.Series) -> int:
|
| 84 |
-
st_dir = int(row["st_direction"])
|
| 85 |
-
rsi = float(row["rsi"])
|
| 86 |
-
|
| 87 |
-
# LONG: SuperTrend UP and RSI pulled back below 45
|
| 88 |
-
if st_dir == 1 and rsi < 45:
|
| 89 |
-
return 1
|
| 90 |
-
|
| 91 |
-
# SHORT: SuperTrend DOWN and RSI bounced above 55
|
| 92 |
-
if st_dir == -1 and rsi > 55:
|
| 93 |
-
return -1
|
| 94 |
-
|
| 95 |
-
return 0
|
| 96 |
-
|
| 97 |
-
def risk_levels(self, entry: float, atr: float, side: int, atr_pct: float = 0.0, row: Optional[pd.Series] = None) -> Tuple[float, float]:
|
| 98 |
-
# 1.5 ATR SL, 3.5 ATR TP (consistent with your successful strategies)
|
| 99 |
-
if side == 1:
|
| 100 |
-
sl = entry - (1.5 * atr)
|
| 101 |
-
tp = entry + (3.5 * atr)
|
| 102 |
-
else:
|
| 103 |
-
sl = entry + (1.5 * atr)
|
| 104 |
-
tp = entry - (3.5 * atr)
|
| 105 |
-
return sl, tp
|
| 106 |
-
|
| 107 |
-
def trail_stop(self, side: int, entry: float, current_sl: float, close: float, atr: float) -> float:
|
| 108 |
-
if side == 1:
|
| 109 |
-
return max(current_sl, close - (2.5 * atr))
|
| 110 |
-
else:
|
| 111 |
-
return min(current_sl, close + (2.5 * atr))
|
| 112 |
-
|
| 113 |
-
def build_intent(self, coin: str, timeframe: str, row: pd.Series) -> Optional[TradeIntent]:
|
| 114 |
-
signal = self.signal_for_row(row)
|
| 115 |
-
if signal == 0:
|
| 116 |
-
return None
|
| 117 |
-
side = "long" if signal == 1 else "short"
|
| 118 |
-
entry = float(row["close"])
|
| 119 |
-
stop, tp = self.risk_levels(entry, float(row["atr"]), signal, 0, row)
|
| 120 |
-
return TradeIntent(coin, timeframe, side, entry, stop, tp, 0.8)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|