Wire legacy trading bot API endpoints
Этот коммит содержится в:
+79
-55
@@ -9,6 +9,7 @@ from app.coinex import CoinExClient, CoinExLiveStream
|
|||||||
from app.core import get_settings
|
from app.core import get_settings
|
||||||
from app.db import SessionLocal, get_db, init_db
|
from app.db import SessionLocal, get_db, init_db
|
||||||
from app.demo_account import DemoAccountService
|
from app.demo_account import DemoAccountService
|
||||||
|
from app.legacy_bot import LegacyBotService
|
||||||
from app.models import DemoTradeRecord
|
from app.models import DemoTradeRecord
|
||||||
|
|
||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
@@ -16,6 +17,7 @@ coinex = CoinExClient(settings.coinex_api_base)
|
|||||||
live_stream = CoinExLiveStream(settings.coinex_ws_spot, coinex)
|
live_stream = CoinExLiveStream(settings.coinex_ws_spot, coinex)
|
||||||
demo_service = DemoAccountService(settings.demo_initial_balance, settings.demo_quote_asset)
|
demo_service = DemoAccountService(settings.demo_initial_balance, settings.demo_quote_asset)
|
||||||
ai_bot = AiTradingBot(coinex, settings.markets)
|
ai_bot = AiTradingBot(coinex, settings.markets)
|
||||||
|
legacy_bot = LegacyBotService()
|
||||||
|
|
||||||
app = FastAPI(title=settings.app_name)
|
app = FastAPI(title=settings.app_name)
|
||||||
static_dir = Path(__file__).parent / 'static'
|
static_dir = Path(__file__).parent / 'static'
|
||||||
@@ -25,6 +27,8 @@ app.mount('/static', StaticFiles(directory=static_dir), name='static')
|
|||||||
@app.on_event('startup')
|
@app.on_event('startup')
|
||||||
def on_startup() -> None:
|
def on_startup() -> None:
|
||||||
init_db()
|
init_db()
|
||||||
|
with SessionLocal() as db:
|
||||||
|
legacy_bot.ensure_defaults(db)
|
||||||
|
|
||||||
|
|
||||||
@app.get('/')
|
@app.get('/')
|
||||||
@@ -34,24 +38,21 @@ async def dashboard() -> FileResponse:
|
|||||||
|
|
||||||
@app.get('/health')
|
@app.get('/health')
|
||||||
async def health() -> dict:
|
async def health() -> dict:
|
||||||
return {
|
return {'status': 'ok', 'app': settings.app_name, 'env': settings.app_env, 'default_market': settings.default_market}
|
||||||
'status': 'ok',
|
|
||||||
'app': settings.app_name,
|
|
||||||
'env': settings.app_env,
|
|
||||||
'default_market': settings.default_market,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@app.get('/api/v1/app/bootstrap')
|
@app.get('/api/v1/app/bootstrap')
|
||||||
async def app_bootstrap(db: Session = Depends(get_db)) -> dict:
|
async def app_bootstrap(db: Session = Depends(get_db)) -> dict:
|
||||||
state = ai_bot.get_or_create_state(db)
|
state = ai_bot.get_or_create_state(db)
|
||||||
account = demo_service.snapshot(db)
|
account = demo_service.snapshot(db)
|
||||||
|
legacy_bot.ensure_defaults(db)
|
||||||
return {
|
return {
|
||||||
'app': settings.app_name,
|
'app': settings.app_name,
|
||||||
'default_market': settings.default_market,
|
'default_market': settings.default_market,
|
||||||
'markets': settings.markets,
|
'markets': settings.markets,
|
||||||
'bot': ai_bot.state_to_dict(state),
|
'bot': ai_bot.state_to_dict(state),
|
||||||
'account': account.model_dump(mode='json'),
|
'account': account.model_dump(mode='json'),
|
||||||
|
'legacy': legacy_bot.dashboard(db),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -66,16 +67,10 @@ async def reset_demo_account(db: Session = Depends(get_db)) -> dict:
|
|||||||
|
|
||||||
|
|
||||||
@app.post('/api/v1/demo/trades')
|
@app.post('/api/v1/demo/trades')
|
||||||
async def create_demo_trade(
|
async def create_demo_trade(market: str, side: str, price: float, amount: float, reason: str = 'manual demo trade', db: Session = Depends(get_db)) -> dict:
|
||||||
market: str,
|
|
||||||
side: str,
|
|
||||||
price: float,
|
|
||||||
amount: float,
|
|
||||||
reason: str = 'manual demo trade',
|
|
||||||
db: Session = Depends(get_db),
|
|
||||||
) -> dict:
|
|
||||||
try:
|
try:
|
||||||
trade = demo_service.add_demo_trade(db=db, market=market, side=side, price=price, amount=amount, reason=reason)
|
trade = demo_service.add_demo_trade(db=db, market=market, side=side, price=price, amount=amount, reason=reason)
|
||||||
|
legacy_bot.snapshot_history(db)
|
||||||
return trade.model_dump(mode='json')
|
return trade.model_dump(mode='json')
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||||
@@ -100,28 +95,10 @@ async def bot_settings(db: Session = Depends(get_db)) -> dict:
|
|||||||
|
|
||||||
|
|
||||||
@app.post('/api/v1/bot/settings')
|
@app.post('/api/v1/bot/settings')
|
||||||
async def update_bot_settings(
|
async def update_bot_settings(enabled: bool | None = None, trade_mode: str | None = None, trade_style_mode: str | None = None, min_signal_score: float | None = None, max_open_positions: int | None = None, max_quote_per_trade: float | None = None, emergency_stop: bool | None = None, db: Session = Depends(get_db)) -> dict:
|
||||||
enabled: bool | None = None,
|
|
||||||
trade_mode: str | None = None,
|
|
||||||
trade_style_mode: str | None = None,
|
|
||||||
min_signal_score: float | None = None,
|
|
||||||
max_open_positions: int | None = None,
|
|
||||||
max_quote_per_trade: float | None = None,
|
|
||||||
emergency_stop: bool | None = None,
|
|
||||||
db: Session = Depends(get_db),
|
|
||||||
) -> dict:
|
|
||||||
if trade_mode is not None and trade_mode not in {'demo', 'live'}:
|
if trade_mode is not None and trade_mode not in {'demo', 'live'}:
|
||||||
raise HTTPException(status_code=400, detail='trade_mode must be demo or live')
|
raise HTTPException(status_code=400, detail='trade_mode must be demo or live')
|
||||||
state = ai_bot.update_state(
|
state = ai_bot.update_state(db, enabled=enabled, trade_mode=trade_mode, trade_style_mode=trade_style_mode, min_signal_score=min_signal_score, max_open_positions=max_open_positions, max_quote_per_trade=max_quote_per_trade, emergency_stop=emergency_stop)
|
||||||
db,
|
|
||||||
enabled=enabled,
|
|
||||||
trade_mode=trade_mode,
|
|
||||||
trade_style_mode=trade_style_mode,
|
|
||||||
min_signal_score=min_signal_score,
|
|
||||||
max_open_positions=max_open_positions,
|
|
||||||
max_quote_per_trade=max_quote_per_trade,
|
|
||||||
emergency_stop=emergency_stop,
|
|
||||||
)
|
|
||||||
return ai_bot.state_to_dict(state)
|
return ai_bot.state_to_dict(state)
|
||||||
|
|
||||||
|
|
||||||
@@ -174,12 +151,8 @@ def _force_demo_trade(db: Session, decision) -> dict:
|
|||||||
decision.executed = True
|
decision.executed = True
|
||||||
db.add(decision)
|
db.add(decision)
|
||||||
db.commit()
|
db.commit()
|
||||||
return {
|
legacy_bot.snapshot_history(db)
|
||||||
'executed': True,
|
return {'executed': True, 'mode': 'demo', 'decision': decision_to_dict(decision), 'trade': trade.model_dump(mode='json')}
|
||||||
'mode': 'demo',
|
|
||||||
'decision': decision_to_dict(decision),
|
|
||||||
'trade': trade.model_dump(mode='json'),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@app.post('/api/v1/bot/auto-demo-trade')
|
@app.post('/api/v1/bot/auto-demo-trade')
|
||||||
@@ -196,25 +169,12 @@ async def auto_trade(market: str | None = None, db: Session = Depends(get_db)) -
|
|||||||
state = ai_bot.get_or_create_state(db)
|
state = ai_bot.get_or_create_state(db)
|
||||||
decision = await ai_bot.make_decision(db, market)
|
decision = await ai_bot.make_decision(db, market)
|
||||||
if state.trade_mode == 'live':
|
if state.trade_mode == 'live':
|
||||||
return {
|
return {'executed': False, 'mode': 'live', 'decision': decision_to_dict(decision), 'message': 'Live mode is selected. CoinEx execution adapter is the next implementation step.'}
|
||||||
'executed': False,
|
|
||||||
'mode': 'live',
|
|
||||||
'decision': decision_to_dict(decision),
|
|
||||||
'message': 'Live mode is selected. Real CoinEx execution adapter is not connected yet; demo execution is stable.',
|
|
||||||
}
|
|
||||||
return _force_demo_trade(db, decision)
|
return _force_demo_trade(db, decision)
|
||||||
|
|
||||||
|
|
||||||
@app.post('/api/v1/signals/manual')
|
@app.post('/api/v1/signals/manual')
|
||||||
async def create_manual_signal(
|
async def create_manual_signal(title: str, market: str, sentiment: str = 'neutral', score: float = 50.0, source: str = 'manual', url: str = '', db: Session = Depends(get_db)) -> dict:
|
||||||
title: str,
|
|
||||||
market: str,
|
|
||||||
sentiment: str = 'neutral',
|
|
||||||
score: float = 50.0,
|
|
||||||
source: str = 'manual',
|
|
||||||
url: str = '',
|
|
||||||
db: Session = Depends(get_db),
|
|
||||||
) -> dict:
|
|
||||||
signal = ai_bot.record_manual_signal(db, title=title, market=market, sentiment=sentiment, score=score, source=source, url=url)
|
signal = ai_bot.record_manual_signal(db, title=title, market=market, sentiment=sentiment, score=score, source=source, url=url)
|
||||||
return signal_to_dict(signal)
|
return signal_to_dict(signal)
|
||||||
|
|
||||||
@@ -238,6 +198,70 @@ async def dashboard_markers(market: str | None = None, limit: int = 100, db: Ses
|
|||||||
return {'items': [trade_marker_from_record(item) for item in reversed(trades)]}
|
return {'items': [trade_marker_from_record(item) for item in reversed(trades)]}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get('/api/v1/legacy/dashboard')
|
||||||
|
async def legacy_dashboard(db: Session = Depends(get_db)) -> dict:
|
||||||
|
return legacy_bot.dashboard(db)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get('/api/v1/legacy/open-trades')
|
||||||
|
async def legacy_open_trades(db: Session = Depends(get_db)) -> dict:
|
||||||
|
return {'items': legacy_bot.open_positions(db)}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get('/api/v1/legacy/closed-trades')
|
||||||
|
async def legacy_closed_trades(limit: int = 50, db: Session = Depends(get_db)) -> dict:
|
||||||
|
return {'items': legacy_bot.closed_positions(db, limit)}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get('/api/v1/legacy/chart-history')
|
||||||
|
async def legacy_chart_history(limit: int = 100, db: Session = Depends(get_db)) -> dict:
|
||||||
|
return {'items': legacy_bot.chart_history(db, limit)}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get('/api/v1/legacy/strategies')
|
||||||
|
async def legacy_strategies(db: Session = Depends(get_db)) -> dict:
|
||||||
|
legacy_bot.ensure_defaults(db)
|
||||||
|
return {'items': legacy_bot.dashboard(db)['strategies']}
|
||||||
|
|
||||||
|
|
||||||
|
@app.post('/api/v1/legacy/strategies/{strategy_id}')
|
||||||
|
async def legacy_update_strategy(strategy_id: int, enabled: bool | None = None, dry_run: bool | None = None, db: Session = Depends(get_db)) -> dict:
|
||||||
|
try:
|
||||||
|
return legacy_bot.update_strategy(db, strategy_id, enabled, dry_run)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||||
|
|
||||||
|
|
||||||
|
@app.get('/api/v1/legacy/coinex')
|
||||||
|
async def legacy_coinex(db: Session = Depends(get_db)) -> dict:
|
||||||
|
return legacy_bot.dashboard(db)['coinex']
|
||||||
|
|
||||||
|
|
||||||
|
@app.post('/api/v1/legacy/coinex')
|
||||||
|
async def legacy_update_coinex(enabled: bool | None = None, access_id: str | None = None, secret_key: str | None = None, mode: str | None = None, db: Session = Depends(get_db)) -> dict:
|
||||||
|
return legacy_bot.update_coinex(db, enabled, access_id, secret_key, mode)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post('/api/v1/legacy/telegram')
|
||||||
|
async def legacy_update_telegram(enabled: bool | None = None, chat_id: str | None = None, message_format: str | None = None, db: Session = Depends(get_db)) -> dict:
|
||||||
|
return legacy_bot.update_telegram(db, enabled, chat_id, message_format)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get('/api/v1/legacy/updater/status')
|
||||||
|
async def legacy_updater_status(db: Session = Depends(get_db)) -> dict:
|
||||||
|
return legacy_bot.updater_status(db)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post('/api/v1/legacy/terminal')
|
||||||
|
async def legacy_terminal(command: str) -> dict:
|
||||||
|
return legacy_bot.terminal_command(command)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get('/api/v1/android/status')
|
||||||
|
async def android_status(db: Session = Depends(get_db)) -> dict:
|
||||||
|
return legacy_bot.android_status(db)
|
||||||
|
|
||||||
|
|
||||||
@app.websocket('/ws/market/{market}')
|
@app.websocket('/ws/market/{market}')
|
||||||
async def market_stream(websocket: WebSocket, market: str) -> None:
|
async def market_stream(websocket: WebSocket, market: str) -> None:
|
||||||
await websocket.accept()
|
await websocket.accept()
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user