freqtrade UI edits

To present frequi to the public, all the problem API calls need to be deleted and the username and password of the bots need to be added to auth_rpc.py so that they are not displayed to the public. Furtheron consider running it in sandbox, or container.

Location of essential edits, these are the API endpoints.:

/home/x/freqtrade/freqtrade/rpc/rpc.py

/home/x/freqtrade/freqtrade/rpc/api_server/

/home/x/freqtrade/freqtrade/rpc/api_server/api_v1.py

/home/x/freqtrade/freqtrade/rpc/api_server/ui

/home/x/freqtrade/.venv/lib/python3.x/site-packages/freqtrade/rpc/api_server/ui

Essentially, it is a matter of making the UI readonly, delete all the functions that control the bot in the python file above and then test their execution in frequi source code...

git clone https://github.com/freqtrade/frequi.git

cd frequi

pnpm install

pnpm run dev

pnpm run build

To add your bots programmically, edit App.vue...

<template>

<div id="app" class="d-flex flex-column dvh-100" :style="colorStore.cssVars">

<NavBar />

<BaseAlert></BaseAlert>

<BodyLayout class="flex-fill overflow-auto" />

<NavFooter />

</div>

</template>

<script setup lang="ts">

import { onMounted, watch } from 'vue';

import { setTimezone } from './shared/formatters';

import { useBotStore } from './stores/ftbotwrapper';

import { useUserService } from './shared/userService';

import { useSettingsStore } from './stores/settings';

import { useColorStore } from './stores/colors';

import { AuthPayload } from '@/types';

const settingsStore = useSettingsStore();

const colorStore = useColorStore();

const botStore = useBotStore();

onMounted(async () => {

setTimezone(settingsStore.timezone);

colorStore.updateProfitLossColor();

localStorage.removeItem('ftAuthLoginInfo');

localStorage.removeItem('ftSelectedBot');

const addBot = async (botName: string, apiUrl: string, username: string, password: string) => {

const auth: AuthPayload = {

botName,

url: apiUrl,

username,

password,

};

const existingBot = Object.values(botStore.availableBots).find(

(bot) => bot.botName === botName && bot.botUrl === apiUrl

);

if (!existingBot) {

try {

const newBotId = botStore.nextBotId;

const userService = useUserService(newBotId);

await userService.login(auth);

botStore.addBot({

botName,

botId: newBotId,

botUrl: apiUrl,

sortId: Object.keys(botStore.availableBots).length + 1,

});

// Set bot's login and online state

botStore.botStores[newBotId].isBotLoggedIn = true;

botStore.botStores[newBotId].isBotOnline = true;

const refreshTokenInterval = async () => {

try {

await userService.refreshToken();

console.log('Token refreshed successfully for bot:', botName);

} catch (error) {

console.error('Failed to refresh token for bot:', botName, error);

}

};

// Set initial token refresh interval

setInterval(refreshTokenInterval, 5 * 60 * 1000);

botStore.selectBot(newBotId);

botStore.allRefreshFull();

console.log('Bot added successfully:', botName);

} catch (error) {

console.error('Failed to add bot:', botName, error);

}

} else {

console.log('Bot already exists:', botName);

}

};

try {

await addBot('My New Bot', 'http://127.0.0.1:8080', 'freqtrader1', 'X9VVzzBGYrB41679dkZ8');

await addBot('Another Bot', 'http://127.0.0.1:8081', 'freqtrader1', 'X9VVzzBGYrB41679dkZ8');

await addBot('Another Bot 2', 'http://127.0.0.1:8082', 'freqtrader1', 'X9VVzzBGYrB41679dkZ8');

} catch (error) {

console.error('Failed to initialize bots:', error);

}

});

watch(

() => settingsStore.timezone,

(tz) => {

console.log('Timezone changed:', tz);

setTimezone(tz);

},

);

</script>

<style scoped>

#app {

font-family: Avenir, Helvetica, Arial, sans-serif;

-webkit-font-smoothing: antialiased;

-moz-osx-font-smoothing: grayscale;

text-align: center;

}

</style>

Untest alt version and reloads the bots every 10 minutes

<template>

<div id="app" class="d-flex flex-column dvh-100" :style="colorStore.cssVars">

<NavBar />

<BaseAlert></BaseAlert>

<BodyLayout class="flex-fill overflow-auto" />

<NavFooter />

</div>

</template>

<script setup lang="ts">

import { onMounted, watch } from 'vue';

import { setTimezone } from './shared/formatters';

import { useBotStore } from './stores/ftbotwrapper';

import { useUserService } from './shared/userService';

import { useSettingsStore } from './stores/settings';

import { useColorStore } from './stores/colors';

import { AuthPayload } from '@/types';

const settingsStore = useSettingsStore();

const colorStore = useColorStore();

const botStore = useBotStore();

const addBot = async (botName: string, apiUrl: string, username: string, password: string) => {

const auth: AuthPayload = {

botName,

url: apiUrl,

username,

password,

};

const existingBot = Object.values(botStore.availableBots).find(

(bot) => bot.botName === botName && bot.botUrl === apiUrl

);

if (!existingBot) {

try {

const newBotId = botStore.nextBotId;

const userService = useUserService(newBotId);

await userService.login(auth);

botStore.addBot({

botName,

botId: newBotId,

botUrl: apiUrl,

sortId: Object.keys(botStore.availableBots).length + 1,

});

// Set bot's login and online state

botStore.botStores[newBotId].isBotLoggedIn = true;

botStore.botStores[newBotId].isBotOnline = true;

const refreshTokenInterval = async () => {

try {

await userService.refreshToken();

console.log('Token refreshed successfully for bot:', botName);

} catch (error) {

console.error('Failed to refresh token for bot:', botName, error);

}

};

// Set initial token refresh interval

setInterval(refreshTokenInterval, 5 * 60 * 1000);

botStore.selectBot(newBotId);

botStore.allRefreshFull();

console.log('Bot added successfully:', botName);

} catch (error) {

console.error('Failed to add bot:', botName, error);

}

} else {

console.log('Bot already exists:', botName);

}

};

const removeAllBots = () => {

const botIds = Object.keys(botStore.availableBots);

botIds.forEach((botId) => {

botStore.removeBot(botId);

});

console.log('All bots removed.');

};

const reAddBots = async () => {

removeAllBots();

try {

await addBot('My New Bot', 'http://127.0.0.1:8080', 'freqtrader1', 'X9VVzzBGYrB41679dkZ8');

await addBot('Another Bot', 'http://127.0.0.1:8081', 'freqtrader1', 'X9VVzzBGYrB41679dkZ8');

await addBot('Another Bot 2', 'http://127.0.0.1:8082', 'freqtrader1', 'X9VVzzBGYrB41679dkZ8');

} catch (error) {

console.error('Failed to re-add bots:', error);

}

};

onMounted(async () => {

setTimezone(settingsStore.timezone);

colorStore.updateProfitLossColor();

localStorage.removeItem('ftAuthLoginInfo');

localStorage.removeItem('ftSelectedBot');

await reAddBots();

// Re-add bots every 10 minutes

setInterval(reAddBots, 10 * 60 * 1000);

});

watch(

() => settingsStore.timezone,

(tz) => {

console.log('Timezone changed:', tz);

setTimezone(tz);

},

);

</script>

<style scoped>

#app {

font-family: Avenir, Helvetica, Arial, sans-serif;

-webkit-font-smoothing: antialiased;

-moz-osx-font-smoothing: grayscale;

text-align: center;

}

</style>

RPC and API Delete

To convert RPC functionality to read-only, remove any method that allows for writing or changing the state of the system. Here's a list of functions that should be removed. /home/x/freqtrade/freqtrade/rpc/rpc.py

_rpc_start: Starts the trading bot.

_rpc_stop: Stops the trading bot.

_rpc_reload_config: Reloads the bot's configuration.

__exec_force_exit: facilitate the forceful exit from a trade.

_rpc_stopentry: Stops buying new positions.

_rpc_reload_trade_from_exchange: Reloads a trade from its orders on the exchange.

_rpc_force_exit: Forces the exit of a trade.

_rpc_force_entry: Forces the entry into a new trade.

_rpc_cancel_open_order: Cancels an open order.

_rpc_delete: Deletes a trade and cancels any open orders associated with it.

_rpc_add_lock: Adds a lock to prevent trading on a particular pair.

_rpc_delete_lock: Removes a lock preventing trading on a particular pair.

_rpc_blacklist_delete: Removes pairs from the blacklist.

_rpc_blacklist (when called with add parameter): Adds pairs to the blacklist.

_update_market_direction: Updates the market direction setting.

Delete the entire function, not just the line.

/home/x/freqtrade/freqtrade/rpc/api_server/api_v1.py

@router.delete("/trades/{tradeid}"

@router.delete("/trades/{tradeid}/open-order"

@router.post("/trades/{tradeid}/reload"

@router.post("/forceenter"

@router.post("/forcebuy"

@router.post("/forceexit"

@router.post("/forcesell"

@router.delete("/blacklist"

@router.delete("/locks/{lockid}"

@router.post("/start"

@router.post("/stop"

@router.post("/stopentry"

@router.post("/stopbuy"

@router.post("/reload_config"

@router.post("/blacklist" .... def blacklist_post

@router.post("/locks/delete"

@router.post("/locks" ..... def add_locks

Files in question...

grep -irln "@router."

freqtrade/rpc/api_server/api_v1.py

freqtrade/rpc/api_server/api_auth.py

freqtrade/rpc/api_server/web_ui.py

freqtrade/rpc/api_server/api_ws.py

freqtrade/rpc/api_server/api_backtest.py

freqtrade/rpc/api_server/api_background_tasks.py

.venv/lib/python3.10/site-packages/fastapi/routing.py

.venv/lib/python3.10/site-packages/fastapi/__pycache__/routing.cpython-310.pyc

grep -ir "@router."

freqtrade/rpc/api_server/api_v1.py:@router_public.get("/ping", response_model=Ping)

freqtrade/rpc/api_server/api_v1.py:@router.get("/version", response_model=Version, tags=["info"])

freqtrade/rpc/api_server/api_v1.py:@router.get("/balance", response_model=Balances, tags=["info"])

freqtrade/rpc/api_server/api_v1.py:@router.get("/count", response_model=Count, tags=["info"])

freqtrade/rpc/api_server/api_v1.py:@router.get("/entries", response_model=List[Entry], tags=["info"])

freqtrade/rpc/api_server/api_v1.py:@router.get("/exits", response_model=List[Exit], tags=["info"])

freqtrade/rpc/api_server/api_v1.py:@router.get("/mix_tags", response_model=List[MixTag], tags=["info"])

freqtrade/rpc/api_server/api_v1.py:@router.get("/performance", response_model=List[PerformanceEntry], tags=["info"])

freqtrade/rpc/api_server/api_v1.py:@router.get("/profit", response_model=Profit, tags=["info"])

freqtrade/rpc/api_server/api_v1.py:@router.get("/stats", response_model=Stats, tags=["info"])

freqtrade/rpc/api_server/api_v1.py:@router.get("/daily", response_model=DailyWeeklyMonthly, tags=["info"])

freqtrade/rpc/api_server/api_v1.py:@router.get("/weekly", response_model=DailyWeeklyMonthly, tags=["info"])

freqtrade/rpc/api_server/api_v1.py:@router.get("/monthly", response_model=DailyWeeklyMonthly, tags=["info"])

freqtrade/rpc/api_server/api_v1.py:@router.get("/status", response_model=List[OpenTradeSchema], tags=["info"])

freqtrade/rpc/api_server/api_v1.py:@router.get("/trades", tags=["info", "trading"])

freqtrade/rpc/api_server/api_v1.py:@router.get("/trade/{tradeid}", response_model=OpenTradeSchema, tags=["info", "trading"])

freqtrade/rpc/api_server/api_v1.py:@router.delete("/trades/{tradeid}", response_model=DeleteTrade, tags=["info", "trading"])

freqtrade/rpc/api_server/api_v1.py:@router.delete("/trades/{tradeid}/open-order", response_model=OpenTradeSchema, tags=["trading"])

freqtrade/rpc/api_server/api_v1.py:@router.post("/trades/{tradeid}/reload", response_model=OpenTradeSchema, tags=["trading"])

freqtrade/rpc/api_server/api_v1.py:@router.get("/edge", tags=["info"])

freqtrade/rpc/api_server/api_v1.py:@router.get("/show_config", response_model=ShowConfig, tags=["info"])

freqtrade/rpc/api_server/api_v1.py:@router.post("/forceenter", response_model=ForceEnterResponse, tags=["trading"])

freqtrade/rpc/api_server/api_v1.py:@router.post("/forcebuy", response_model=ForceEnterResponse, tags=["trading"])

freqtrade/rpc/api_server/api_v1.py:@router.post("/forceexit", response_model=ResultMsg, tags=["trading"])

freqtrade/rpc/api_server/api_v1.py:@router.post("/forcesell", response_model=ResultMsg, tags=["trading"])

freqtrade/rpc/api_server/api_v1.py:@router.get("/blacklist", response_model=BlacklistResponse, tags=["info", "pairlist"])

freqtrade/rpc/api_server/api_v1.py:@router.post("/blacklist", response_model=BlacklistResponse, tags=["info", "pairlist"])

freqtrade/rpc/api_server/api_v1.py:@router.delete("/blacklist", response_model=BlacklistResponse, tags=["info", "pairlist"])

freqtrade/rpc/api_server/api_v1.py:@router.get("/whitelist", response_model=WhitelistResponse, tags=["info", "pairlist"])

freqtrade/rpc/api_server/api_v1.py:@router.get("/locks", response_model=Locks, tags=["info", "locks"])

freqtrade/rpc/api_server/api_v1.py:@router.delete("/locks/{lockid}", response_model=Locks, tags=["info", "locks"])

freqtrade/rpc/api_server/api_v1.py:@router.post("/locks/delete", response_model=Locks, tags=["info", "locks"])

freqtrade/rpc/api_server/api_v1.py:@router.post("/locks", response_model=Locks, tags=["info", "locks"])

freqtrade/rpc/api_server/api_v1.py:@router.get("/logs", response_model=Logs, tags=["info"])

freqtrade/rpc/api_server/api_v1.py:@router.post("/start", response_model=StatusMsg, tags=["botcontrol"])

freqtrade/rpc/api_server/api_v1.py:@router.post("/stop", response_model=StatusMsg, tags=["botcontrol"])

freqtrade/rpc/api_server/api_v1.py:@router.post("/stopentry", response_model=StatusMsg, tags=["botcontrol"])

freqtrade/rpc/api_server/api_v1.py:@router.post("/stopbuy", response_model=StatusMsg, tags=["botcontrol"])

freqtrade/rpc/api_server/api_v1.py:@router.post("/reload_config", response_model=StatusMsg, tags=["botcontrol"])

freqtrade/rpc/api_server/api_v1.py:@router.get("/pair_candles", response_model=PairHistory, tags=["candle data"])

freqtrade/rpc/api_server/api_v1.py:@router.post("/pair_candles", response_model=PairHistory, tags=["candle data"])

freqtrade/rpc/api_server/api_v1.py:@router.get("/pair_history", response_model=PairHistory, tags=["candle data"])

freqtrade/rpc/api_server/api_v1.py:@router.post("/pair_history", response_model=PairHistory, tags=["candle data"])

freqtrade/rpc/api_server/api_v1.py:@router.get("/plot_config", response_model=PlotConfig, tags=["candle data"])

freqtrade/rpc/api_server/api_v1.py:@router.get("/strategies", response_model=StrategyListResponse, tags=["strategy"])

freqtrade/rpc/api_server/api_v1.py:@router.get("/strategy/{strategy}", response_model=StrategyResponse, tags=["strategy"])

freqtrade/rpc/api_server/api_v1.py:@router.get("/exchanges", response_model=ExchangeListResponse, tags=[])

freqtrade/rpc/api_server/api_v1.py:@router.get("/freqaimodels", response_model=FreqAIModelListResponse, tags=["freqai"])

freqtrade/rpc/api_server/api_v1.py:@router.get("/available_pairs", response_model=AvailablePairs, tags=["candle data"])

freqtrade/rpc/api_server/api_v1.py:@router.get("/sysinfo", response_model=SysInfo, tags=["info"])

freqtrade/rpc/api_server/api_v1.py:@router.get("/health", response_model=Health, tags=["info"])

freqtrade/rpc/api_server/api_auth.py:@router_login.post("/token/login", response_model=AccessAndRefreshToken)

freqtrade/rpc/api_server/api_auth.py:@router_login.post("/token/refresh", response_model=AccessToken)

freqtrade/rpc/api_server/web_ui.py:@router_ui.get("/favicon.ico", include_in_schema=False)

freqtrade/rpc/api_server/web_ui.py:@router_ui.get("/fallback_file.html", include_in_schema=False)

freqtrade/rpc/api_server/web_ui.py:@router_ui.get("/ui_version", include_in_schema=False)

freqtrade/rpc/api_server/web_ui.py:@router_ui.get("/{rest_of_path:path}", include_in_schema=False)

freqtrade/rpc/api_server/api_ws.py:@router.websocket("/message/ws")

freqtrade/rpc/api_server/api_backtest.py:@router.post("/backtest", response_model=BacktestResponse, tags=["webserver", "backtest"])

freqtrade/rpc/api_server/api_backtest.py:@router.get("/backtest", response_model=BacktestResponse, tags=["webserver", "backtest"])

freqtrade/rpc/api_server/api_backtest.py:@router.delete("/backtest", response_model=BacktestResponse, tags=["webserver", "backtest"])

freqtrade/rpc/api_server/api_backtest.py:@router.get("/backtest/abort", response_model=BacktestResponse, tags=["webserver", "backtest"])

freqtrade/rpc/api_server/api_backtest.py:@router.get(

freqtrade/rpc/api_server/api_backtest.py:@router.get(

freqtrade/rpc/api_server/api_backtest.py:@router.delete(

freqtrade/rpc/api_server/api_backtest.py:@router.patch(

freqtrade/rpc/api_server/api_backtest.py:@router.get(

freqtrade/rpc/api_server/api_background_tasks.py:@router.get("/background", response_model=List[BackgroundTaskStatus], tags=["webserver"])

freqtrade/rpc/api_server/api_background_tasks.py:@router.get("/background/{jobid}", response_model=BackgroundTaskStatus, tags=["webserver"])

freqtrade/rpc/api_server/api_background_tasks.py:@router.get(

freqtrade/rpc/api_server/api_background_tasks.py:@router.post("/pairlists/evaluate", response_model=BgJobStarted, tags=["pairlists", "webserver"])

freqtrade/rpc/api_server/api_background_tasks.py:@router.get(

.venv/lib/python3.10/site-packages/fastapi/routing.py: @router.get("/users/", tags=["users"])

.venv/lib/python3.10/site-packages/fastapi/routing.py: @router.websocket("/ws")

.venv/lib/python3.10/site-packages/fastapi/routing.py: @router.get("/items/")

.venv/lib/python3.10/site-packages/fastapi/routing.py: @router.put("/items/{item_id}")

.venv/lib/python3.10/site-packages/fastapi/routing.py: @router.post("/items/")

.venv/lib/python3.10/site-packages/fastapi/routing.py: @router.delete("/items/{item_id}")

.venv/lib/python3.10/site-packages/fastapi/routing.py: @router.options("/items/")

.venv/lib/python3.10/site-packages/fastapi/routing.py: @router.head("/items/", status_code=204)

.venv/lib/python3.10/site-packages/fastapi/routing.py: @router.patch("/items/")

.venv/lib/python3.10/site-packages/fastapi/routing.py: @router.trace("/items/{item_id}")

To autologin:

hardcode username and password that matches the bot in the file api_auth.py

regardless of the username and password entered in the frequi, the login will be successful.

To autoadd the bot in frequi, edit the source and programmically add,

// Strategy 1

const botId1 = 'ftbot.1';

const auth1 = new Un(botId1);

const loginData1 = {

botName: 'Strategy1',

url: 'http://127.0.0.1:8080',

username: 'freqtrader',

password: 'password'

};

auth1.login(loginData1)

.then(() => {

console.log('Login successful for Strategy1');

// Ensure tokens are stored

console.log('Access Token:', auth1.getAccessToken());

console.log('Refresh Token:', auth1.getRefreshToken());

console.log('Set Token:', auth1.setAccessToken());

// Continue with further operations after login

})

.catch((error) => {

console.error('Login failed for Strategy1:', error);

// Handle login error for Strategy1

});

repeat for each bot.

@router.delete("/trades/{tradeid}", response_model=DeleteTrade, tags=["info", "trading"])

def trades_delete(tradeid: int, rpc: RPC = Depends(get_rpc)):

return rpc._rpc_delete(tradeid)

@router.delete("/trades/{tradeid}/open-order", response_model=OpenTradeSchema, tags=["trading"])

def trade_cancel_open_order(tradeid: int, rpc: RPC = Depends(get_rpc)):

rpc._rpc_cancel_open_order(tradeid)

return rpc._rpc_trade_status([tradeid])[0]

@router.post("/trades/{tradeid}/reload", response_model=OpenTradeSchema, tags=["trading"])

def trade_reload(tradeid: int, rpc: RPC = Depends(get_rpc)):

rpc._rpc_reload_trade_from_exchange(tradeid)

return rpc._rpc_trade_status([tradeid])[0]

# /forcebuy is deprecated with short addition. use /forceentry instead

@router.post("/forceenter", response_model=ForceEnterResponse, tags=["trading"])

@router.post("/forcebuy", response_model=ForceEnterResponse, tags=["trading"])

def force_entry(payload: ForceEnterPayload, rpc: RPC = Depends(get_rpc)):

ordertype = payload.ordertype.value if payload.ordertype else None

trade = rpc._rpc_force_entry(

payload.pair,

payload.price,

order_side=payload.side,

order_type=ordertype,

stake_amount=payload.stakeamount,

enter_tag=payload.entry_tag or "force_entry",

leverage=payload.leverage,

)

if trade:

return ForceEnterResponse.model_validate(trade.to_json())

else:

return ForceEnterResponse.model_validate(

{"status": f"Error entering {payload.side} trade for pair {payload.pair}."}

)

# /forcesell is deprecated with short addition. use /forceexit instead

@router.post("/forceexit", response_model=ResultMsg, tags=["trading"])

@router.post("/forcesell", response_model=ResultMsg, tags=["trading"])

def forceexit(payload: ForceExitPayload, rpc: RPC = Depends(get_rpc)):

ordertype = payload.ordertype.value if payload.ordertype else None

return rpc._rpc_force_exit(str(payload.tradeid), ordertype, amount=payload.amount)

@router.post("/blacklist", response_model=BlacklistResponse, tags=["info", "pairlist"])

def blacklist_post(payload: BlacklistPayload, rpc: RPC = Depends(get_rpc)):

return rpc._rpc_blacklist(payload.blacklist)

@router.delete("/blacklist", response_model=BlacklistResponse, tags=["info", "pairlist"])

def blacklist_delete(pairs_to_delete: list[str] = Query([]), rpc: RPC = Depends(get_rpc)):

"""Provide a list of pairs to delete from the blacklist"""

return rpc._rpc_blacklist_delete(pairs_to_delete)

@router.delete("/locks/{lockid}", response_model=Locks, tags=["info", "locks"])

def delete_lock(lockid: int, rpc: RPC = Depends(get_rpc)):

return rpc._rpc_delete_lock(lockid=lockid)

@router.post("/locks/delete", response_model=Locks, tags=["info", "locks"])

def delete_lock_pair(payload: DeleteLockRequest, rpc: RPC = Depends(get_rpc)):

return rpc._rpc_delete_lock(lockid=payload.lockid, pair=payload.pair)

@router.post("/locks", response_model=Locks, tags=["info", "locks"])

def add_locks(payload: list[LocksPayload], rpc: RPC = Depends(get_rpc)):

for lock in payload:

rpc._rpc_add_lock(lock.pair, lock.until, lock.reason, lock.side)

return rpc._rpc_locks()

@router.post("/start", response_model=StatusMsg, tags=["botcontrol"])

def start(rpc: RPC = Depends(get_rpc)):

return rpc._rpc_start()

@router.post("/stop", response_model=StatusMsg, tags=["botcontrol"])

def stop(rpc: RPC = Depends(get_rpc)):

return rpc._rpc_stop()

@router.post("/stopentry", response_model=StatusMsg, tags=["botcontrol"])

@router.post("/stopbuy", response_model=StatusMsg, tags=["botcontrol"])

def stop_buy(rpc: RPC = Depends(get_rpc)):

return rpc._rpc_stopentry()

@router.post("/reload_config", response_model=StatusMsg, tags=["botcontrol"])

def reload_config(rpc: RPC = Depends(get_rpc)):

return rpc._rpc_reload_config()

  

📝 📜 ⏱️ ⬆️