fix: 해외 cash=0.00 및 get_open_position HOLD 필터링 수정 (#264, #265)
Some checks failed
CI / test (pull_request) Has been cancelled
Some checks failed
CI / test (pull_request) Has been cancelled
## 변경사항 ### #264 — 해외 매수가능금액 조회 API 교체 (frcr_dncl_amt_2 → inquire-psamount) - TTTS3012R (해외주식 잔고) output2에 frcr_dncl_amt_2 필드가 존재하지 않아 총 가용 현금이 항상 0.00으로 산출되는 문제 수정 - OverseasBroker에 get_overseas_buying_power() 메서드 추가 (TR_ID: 실전 TTTS3007R / 모의 VTTS3007R, ord_psbl_frcr_amt 반환) - main.py trading_cycle() 및 daily cycle 모두 수정 - 출처: 한국투자증권 오픈API 전체문서 (20260221) — 해외주식 매수가능금액조회 시트 ### #265 — get_open_position() HOLD 레코드 필터링 추가 - HOLD 결정도 trades 테이블에 저장되어 BUY 이후 HOLD 기록 시 최신 레코드가 HOLD → get_open_position이 None 반환하는 문제 수정 - 쿼리에 AND action IN ('BUY', 'SELL') 필터 추가 - HOLD 레코드를 제외하고 마지막 BUY/SELL 기록만 확인 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -903,12 +903,14 @@ class TestOverseasBalanceParsing:
|
||||
"output2": [
|
||||
{
|
||||
"frcr_evlu_tota": "10000.00",
|
||||
"frcr_dncl_amt_2": "5000.00",
|
||||
"frcr_buy_amt_smtl": "4500.00",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
broker.get_overseas_buying_power = AsyncMock(
|
||||
return_value={"output": {"ord_psbl_frcr_amt": "5000.00"}}
|
||||
)
|
||||
return broker
|
||||
|
||||
@pytest.fixture
|
||||
@@ -922,11 +924,13 @@ class TestOverseasBalanceParsing:
|
||||
return_value={
|
||||
"output2": {
|
||||
"frcr_evlu_tota": "10000.00",
|
||||
"frcr_dncl_amt_2": "5000.00",
|
||||
"frcr_buy_amt_smtl": "4500.00",
|
||||
}
|
||||
}
|
||||
)
|
||||
broker.get_overseas_buying_power = AsyncMock(
|
||||
return_value={"output": {"ord_psbl_frcr_amt": "5000.00"}}
|
||||
)
|
||||
return broker
|
||||
|
||||
@pytest.fixture
|
||||
@@ -937,6 +941,9 @@ class TestOverseasBalanceParsing:
|
||||
return_value={"output": {"last": "150.50"}}
|
||||
)
|
||||
broker.get_overseas_balance = AsyncMock(return_value={"output2": []})
|
||||
broker.get_overseas_buying_power = AsyncMock(
|
||||
return_value={"output": {"ord_psbl_frcr_amt": "0.00"}}
|
||||
)
|
||||
return broker
|
||||
|
||||
@pytest.fixture
|
||||
@@ -951,12 +958,15 @@ class TestOverseasBalanceParsing:
|
||||
"output2": [
|
||||
{
|
||||
"frcr_evlu_tota": "10000.00",
|
||||
"frcr_dncl_amt_2": "5000.00",
|
||||
"frcr_buy_amt_smtl": "4500.00",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
# get_overseas_buying_power not called when price=0, but mock for safety
|
||||
broker.get_overseas_buying_power = AsyncMock(
|
||||
return_value={"output": {"ord_psbl_frcr_amt": "5000.00"}}
|
||||
)
|
||||
return broker
|
||||
|
||||
@pytest.fixture
|
||||
@@ -1186,12 +1196,14 @@ class TestOverseasBalanceParsing:
|
||||
"output2": [
|
||||
{
|
||||
"frcr_evlu_tota": "100000.00",
|
||||
"frcr_dncl_amt_2": "50000.00",
|
||||
"frcr_buy_amt_smtl": "50000.00",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
broker.get_overseas_buying_power = AsyncMock(
|
||||
return_value={"output": {"ord_psbl_frcr_amt": "50000.00"}}
|
||||
)
|
||||
broker.send_overseas_order = AsyncMock(return_value={"msg1": "주문접수"})
|
||||
return broker
|
||||
|
||||
@@ -1291,12 +1303,14 @@ class TestOverseasBalanceParsing:
|
||||
"output2": [
|
||||
{
|
||||
"frcr_evlu_tota": "100000.00",
|
||||
"frcr_dncl_amt_2": "50000.00",
|
||||
"frcr_buy_amt_smtl": "50000.00",
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
overseas_broker.get_overseas_buying_power = AsyncMock(
|
||||
return_value={"output": {"ord_psbl_frcr_amt": "50000.00"}}
|
||||
)
|
||||
overseas_broker.send_overseas_order = AsyncMock(
|
||||
return_value={"rt_cd": "0", "msg1": "OK"}
|
||||
)
|
||||
@@ -1355,9 +1369,12 @@ class TestOverseasBalanceParsing:
|
||||
overseas_broker.get_overseas_balance = AsyncMock(
|
||||
return_value={
|
||||
"output1": [],
|
||||
"output2": [{"frcr_evlu_tota": "0", "frcr_dncl_amt_2": "10000", "frcr_buy_amt_smtl": "0"}],
|
||||
"output2": [{"frcr_evlu_tota": "0", "frcr_buy_amt_smtl": "0"}],
|
||||
}
|
||||
)
|
||||
overseas_broker.get_overseas_buying_power = AsyncMock(
|
||||
return_value={"output": {"ord_psbl_frcr_amt": "10000"}}
|
||||
)
|
||||
overseas_broker.get_overseas_price = AsyncMock(
|
||||
return_value={"output": {"last": "50.1234", "rate": "0"}}
|
||||
)
|
||||
@@ -1413,9 +1430,12 @@ class TestOverseasBalanceParsing:
|
||||
overseas_broker.get_overseas_balance = AsyncMock(
|
||||
return_value={
|
||||
"output1": [],
|
||||
"output2": [{"frcr_evlu_tota": "0", "frcr_dncl_amt_2": "10000", "frcr_buy_amt_smtl": "0"}],
|
||||
"output2": [{"frcr_evlu_tota": "0", "frcr_buy_amt_smtl": "0"}],
|
||||
}
|
||||
)
|
||||
overseas_broker.get_overseas_buying_power = AsyncMock(
|
||||
return_value={"output": {"ord_psbl_frcr_amt": "10000"}}
|
||||
)
|
||||
overseas_broker.get_overseas_price = AsyncMock(
|
||||
return_value={"output": {"last": "0.5678", "rate": "0"}}
|
||||
)
|
||||
@@ -2781,9 +2801,11 @@ class TestBuyCooldown:
|
||||
)
|
||||
broker.get_overseas_balance = AsyncMock(return_value={
|
||||
"output1": [],
|
||||
"output2": [{"frcr_dncl_amt_2": "50000", "frcr_evlu_tota": "50000",
|
||||
"frcr_buy_amt_smtl": "0"}],
|
||||
"output2": [{"frcr_evlu_tota": "50000", "frcr_buy_amt_smtl": "0"}],
|
||||
})
|
||||
broker.get_overseas_buying_power = AsyncMock(
|
||||
return_value={"output": {"ord_psbl_frcr_amt": "50000"}}
|
||||
)
|
||||
broker.send_overseas_order = AsyncMock(
|
||||
return_value={"rt_cd": "1", "msg1": "모의투자 주문가능금액이 부족합니다."}
|
||||
)
|
||||
@@ -2896,9 +2918,11 @@ class TestBuyCooldown:
|
||||
)
|
||||
overseas_broker.get_overseas_balance = AsyncMock(return_value={
|
||||
"output1": [],
|
||||
"output2": [{"frcr_dncl_amt_2": "50000", "frcr_evlu_tota": "50000",
|
||||
"frcr_buy_amt_smtl": "0"}],
|
||||
"output2": [{"frcr_evlu_tota": "50000", "frcr_buy_amt_smtl": "0"}],
|
||||
})
|
||||
overseas_broker.get_overseas_buying_power = AsyncMock(
|
||||
return_value={"output": {"ord_psbl_frcr_amt": "50000"}}
|
||||
)
|
||||
overseas_broker.send_overseas_order = AsyncMock(
|
||||
return_value={"rt_cd": "1", "msg1": "기타 오류 메시지"}
|
||||
)
|
||||
@@ -3293,9 +3317,12 @@ async def test_buy_suppressed_when_open_position_exists() -> None:
|
||||
overseas_broker.get_overseas_balance = AsyncMock(
|
||||
return_value={
|
||||
"output1": [],
|
||||
"output2": [{"frcr_dncl_amt_2": "10000", "frcr_evlu_tota": "10000", "frcr_buy_amt_smtl": "0"}],
|
||||
"output2": [{"frcr_evlu_tota": "10000", "frcr_buy_amt_smtl": "0"}],
|
||||
}
|
||||
)
|
||||
overseas_broker.get_overseas_buying_power = AsyncMock(
|
||||
return_value={"output": {"ord_psbl_frcr_amt": "10000"}}
|
||||
)
|
||||
overseas_broker.send_overseas_order = AsyncMock(return_value={"msg1": "OK"})
|
||||
|
||||
engine = MagicMock(spec=ScenarioEngine)
|
||||
@@ -3357,9 +3384,12 @@ async def test_buy_proceeds_when_no_open_position() -> None:
|
||||
overseas_broker.get_overseas_balance = AsyncMock(
|
||||
return_value={
|
||||
"output1": [],
|
||||
"output2": [{"frcr_dncl_amt_2": "50000", "frcr_evlu_tota": "50000", "frcr_buy_amt_smtl": "0"}],
|
||||
"output2": [{"frcr_evlu_tota": "50000", "frcr_buy_amt_smtl": "0"}],
|
||||
}
|
||||
)
|
||||
overseas_broker.get_overseas_buying_power = AsyncMock(
|
||||
return_value={"output": {"ord_psbl_frcr_amt": "50000"}}
|
||||
)
|
||||
overseas_broker.send_overseas_order = AsyncMock(return_value={"msg1": "OK"})
|
||||
|
||||
engine = MagicMock(spec=ScenarioEngine)
|
||||
@@ -3460,13 +3490,15 @@ class TestOverseasBrokerIntegration:
|
||||
"output1": [{"ovrs_pdno": "AAPL", "ovrs_cblc_qty": "10"}],
|
||||
"output2": [
|
||||
{
|
||||
"frcr_dncl_amt_2": "50000.00",
|
||||
"frcr_evlu_tota": "60000.00",
|
||||
"frcr_buy_amt_smtl": "50000.00",
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
overseas_broker.get_overseas_buying_power = AsyncMock(
|
||||
return_value={"output": {"ord_psbl_frcr_amt": "50000.00"}}
|
||||
)
|
||||
overseas_broker.send_overseas_order = AsyncMock(return_value={"msg1": "주문접수"})
|
||||
|
||||
engine = MagicMock(spec=ScenarioEngine)
|
||||
@@ -3534,13 +3566,15 @@ class TestOverseasBrokerIntegration:
|
||||
"output1": [],
|
||||
"output2": [
|
||||
{
|
||||
"frcr_dncl_amt_2": "50000.00",
|
||||
"frcr_evlu_tota": "50000.00",
|
||||
"frcr_buy_amt_smtl": "0.00",
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
overseas_broker.get_overseas_buying_power = AsyncMock(
|
||||
return_value={"output": {"ord_psbl_frcr_amt": "50000.00"}}
|
||||
)
|
||||
overseas_broker.send_overseas_order = AsyncMock(return_value={"msg1": "주문접수"})
|
||||
|
||||
engine = MagicMock(spec=ScenarioEngine)
|
||||
@@ -3963,7 +3997,6 @@ class TestSyncPositionsFromBroker:
|
||||
"output2": [
|
||||
{
|
||||
"frcr_evlu_tota": "50000",
|
||||
"frcr_dncl_amt_2": "10000",
|
||||
"frcr_buy_amt_smtl": "40000",
|
||||
}
|
||||
],
|
||||
@@ -4131,7 +4164,7 @@ class TestSyncPositionsFromBroker:
|
||||
|
||||
balance = {
|
||||
"output1": [{"ovrs_pdno": "AAPL", "ovrs_cblc_qty": "10", "pchs_avg_pric": "170.0"}],
|
||||
"output2": [{"frcr_evlu_tota": "50000", "frcr_dncl_amt_2": "10000", "frcr_buy_amt_smtl": "40000"}],
|
||||
"output2": [{"frcr_evlu_tota": "50000", "frcr_buy_amt_smtl": "40000"}],
|
||||
}
|
||||
broker = MagicMock()
|
||||
overseas_broker = MagicMock()
|
||||
@@ -4789,6 +4822,9 @@ class TestOverseasGhostPositionClose:
|
||||
return_value={"output": {"last": str(current_price), "rate": "0.0"}}
|
||||
)
|
||||
ob.get_overseas_balance = AsyncMock(return_value=balance_data)
|
||||
ob.get_overseas_buying_power = AsyncMock(
|
||||
return_value={"output": {"ord_psbl_frcr_amt": "0.00"}}
|
||||
)
|
||||
ob.send_overseas_order = AsyncMock(return_value=sell_result)
|
||||
return ob
|
||||
|
||||
@@ -4811,7 +4847,7 @@ class TestOverseasGhostPositionClose:
|
||||
"output1": [
|
||||
{"ovrs_pdno": stock_code, "ord_psbl_qty": "5", "ovrs_cblc_qty": "5"}
|
||||
],
|
||||
"output2": [{"tot_evlu_amt": "10000", "frcr_dncl_amt_2": "10000"}],
|
||||
"output2": [{"tot_evlu_amt": "10000"}],
|
||||
}
|
||||
sell_result = {"rt_cd": "1", "msg1": "모의투자 잔고내역이 없습니다"}
|
||||
|
||||
@@ -4887,7 +4923,7 @@ class TestOverseasGhostPositionClose:
|
||||
current_price = 250.0
|
||||
balance_data = {
|
||||
"output1": [{"ovrs_pdno": stock_code, "ord_psbl_qty": "5", "ovrs_cblc_qty": "5"}],
|
||||
"output2": [{"tot_evlu_amt": "100000", "frcr_dncl_amt_2": "100000"}],
|
||||
"output2": [{"tot_evlu_amt": "100000"}],
|
||||
}
|
||||
sell_result = {"rt_cd": "1", "msg1": "일시적 오류가 발생했습니다"}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user