Alpaca ยท Risk Guards ยท Bracket Order ยท ํ์ต ๋ฐํ
W7์ Orchestrator๊ฐ BUY_STRONG/SELL_STRONG์ ๋ผ ๋ Risk Guards๋ฅผ ํต๊ณผํ ์๊ทธ๋๋ง Alpaca paper ๊ณ์ข์ ์๋ ๋ฐ์ฃผํฉ๋๋ค. Bracket Order(์ง์ +์ต์ +์์ ๋์ ๋ฑ๋ก)๋ก ์ง์ ์ฆ์ ์ถ๊ตฌ๋ ์ ํด๋ก๋๋ค.
| ๊ฐ๋ | ์ฒดํฌ | ์๋ฐ ์ |
|---|---|---|
| 1. ๋จ์ผ ์ข ๋ชฉ ๋น์ค | ์ด๋ฒ ์ฃผ๋ฌธ ํ ๋จ์ผ ์ข ๋ชฉ ๋น์ค โค 10% | ์ฃผ๋ฌธ ์ทจ์ + Slack ๊ฒฝ๊ณ |
| 2. ์ผ์ผ ์ฒด๊ฒฐ ํ์ | ๋น์ผ ์ฒด๊ฒฐ โค 5๊ฑด | ๋น์ผ ์ถ๊ฐ ์ฃผ๋ฌธ ์ฐจ๋จ |
| 3. ๋ณ๋์ฑ ์๊ณ | 20์ผ ๋ณ๋์ฑ โค 5% | ์ฃผ๋ฌธ ์๋ ์ ๋ฐ |
| 4. DDL (Daily Drawdown Limit) | ๋น์ผ ํ๊ฐ์์ต โฅ -2% | ๋งค์ ์ ๋ฉด ์ค๋จ (์์ ๋ชจ๋) |
4๊ฐ์ง ๋ชจ๋ ํต๊ณผํ ์๊ทธ๋๋ง Bracket Order๋ก ๋ฐ์ฃผ. ํ๋๋ผ๋ ์คํจ ์ ์ฌ์ ์ ํจ๊ป Slack ์๋ฆผ.
ํ ๋ฒ์ ์ฃผ๋ฌธ์ 3๊ฐ ๋ค๋ฆฌ๊ฐ ๋์ ๋ฑ๋ก: 1. Entry โ ์์ฅ๊ฐ ๋๋ ์ง์ ๊ฐ ๋งค์/๋งค๋ 2. Take Profit โ +5% ์์ต ์ ์๋ ๋งค๋ 3. Stop Loss โ -2% ์์ค ์ ์๋ ๋งค๋ โ ์ง์ ์งํ๋ถํฐ ๋ณดํธ์ ์ด ์ด์์์ โ ์ธ๊ฐ ๊ฐ์ ์์ด๋ ์์ค ํ์
POST https://paper-api.alpaca.markets/v2/orders
APCA-API-KEY-ID: {KEY}
APCA-API-SECRET-KEY: {SECRET}
Content-Type: application/json
{
"symbol": "AAPL",
"qty": 10,
"side": "buy",
"type": "market",
"time_in_force": "gtc",
"order_class": "bracket",
"take_profit": { "limit_price": "{{entry * 1.05}}" },
"stop_loss": { "stop_price": "{{entry * 0.98}}" }
}
// ์
๋ ฅ: {ticker, side, qty, price}, position[], account
// ์ถ๋ ฅ: {pass: true|false, failed_guard?, reason?}
const orderValue = qty * price;
const portfolioValue = +account.portfolio_value;
// Guard 1: ๋จ์ผ ์ข
๋ชฉ ๋น์ค โค 10%
const myPosVal = positions.filter(p => p.symbol===ticker)
.reduce((s,p) => s + +p.market_value, 0);
const newConcentration = (myPosVal + orderValue) / portfolioValue;
if (newConcentration > 0.10) {
return {pass:false, failed_guard:1, reason:`single ${(newConcentration*100).toFixed(1)}%`};
}
// Guard 2: ์ผ์ผ ์ฒด๊ฒฐ ํ์ โค 5
const todayFills = await getAlpacaFillsToday(); // helper
if (todayFills.length >= 5) {
return {pass:false, failed_guard:2, reason:`today ${todayFills.length} fills`};
}
// Guard 3: ๋ณ๋์ฑ โค 5%
const vol20 = await get20DayVolatility(ticker); // helper from W2
if (vol20 > 0.05) {
// ์๋ ์ ๋ฐ์ผ๋ก
qty = Math.floor(qty/2);
}
// Guard 4: DDL โฅ -2%
const dailyPnL = (+account.equity - +account.last_equity) / +account.last_equity;
if (dailyPnL < -0.02 && side === 'buy') {
return {pass:false, failed_guard:4, reason:`DDL ${(dailyPnL*100).toFixed(1)}%`};
}
return {pass:true, qty};