diff --git a/trading-positions@trading-journal/files/trading-positions@trading-journal/desklet.js b/trading-positions@trading-journal/files/trading-positions@trading-journal/desklet.js new file mode 100644 index 000000000..11420e6c3 --- /dev/null +++ b/trading-positions@trading-journal/files/trading-positions@trading-journal/desklet.js @@ -0,0 +1,391 @@ +// Trading Positions Desklet +// Shows open positions from Crypto Trading Journal +// Requires: Cinnamon 5.0+ + +const Desklet = imports.ui.desklet; +const St = imports.gi.St; +const GLib = imports.gi.GLib; +const Gio = imports.gi.Gio; +const Mainloop = imports.mainloop; +const Settings = imports.ui.settings; +const Gettext = imports.gettext; + +// Soup version detection: Cinnamon < 5.8 uses Soup 2, newer uses Soup 3 +let Soup; +try { + imports.gi.versions.Soup = '3.0'; + Soup = imports.gi.Soup; + var SOUP_VERSION = 3; +} catch(e) { + Soup = imports.gi.Soup; + var SOUP_VERSION = 2; +} + +const UUID = "trading-positions@trading-journal"; +const APP_VERSION = "2.7.1"; +const APP_NAME = "Crypto Trading Journal"; + +Gettext.bindtextdomain(UUID, GLib.get_user_data_dir() + "/locale"); + +function _(str) { + return Gettext.dgettext(UUID, str); +} + +class TradingPositionsDesklet extends Desklet.Desklet { + + constructor(metadata, desklet_id) { + super(metadata, desklet_id); + this._metadata = metadata; + + // Settings + this._settings = new Settings.DeskletSettings(this, UUID, desklet_id); + this._settings.bind("server-host", "serverHost", this._onSettingChanged.bind(this)); + this._settings.bind("server-port", "serverPort", this._onSettingChanged.bind(this)); + this._settings.bind("refresh-interval", "refreshInterval", this._onSettingChanged.bind(this)); + this._settings.bind("show-leverage", "showLeverage", this._onSettingChanged.bind(this)); + this._settings.bind("show-mark-price", "showMarkPrice", this._onSettingChanged.bind(this)); + this._settings.bind("show-github-link", "showGithubLink", this._onFooterChanged.bind(this)); + this._settings.bind("font-size", "fontSize", this._onStyleChanged.bind(this)); + this._settings.bind("font-size-ui", "fontSizeUi", this._onStyleChanged.bind(this)); + this._settings.bind("bg-opacity", "bgOpacity", this._onStyleChanged.bind(this)); + this._settings.bind("min-width", "minWidth", this._onStyleChanged.bind(this)); + + this._cookieAcquired = false; + this._timeoutId = null; + + this._initSoup(); + this._buildUI(); + this._applyStyle(); + this._startPolling(); + } + + get _baseUrl() { + let host = (this.serverHost || 'localhost').trim(); + return `http://${host}:${this.serverPort}`; + } + + // ------------------------------------------------------------------ Style + + _applyStyle() { + let opacity = Math.max(0, Math.min(100, this.bgOpacity ?? 88)) / 100; + let fs = this.fontSize ?? 12; + let fsUi = this.fontSizeUi ?? 11; + let mw = this.minWidth ?? 400; + this._outer.set_style( + `background-color: rgba(15,20,28,${opacity.toFixed(2)});` + + `min-width: ${mw}px;` + ); + // Scale column widths based on font size (base: fs=12) + let scale = fs / 12; + this._colWidths = { + symbol: Math.round(90 * scale), + side: Math.round(55 * scale), + leverage: Math.round(36 * scale), + price: Math.round(78 * scale), + pnl: Math.round(95 * scale), + }; + // Positions + this._posFs = fs; + // UI elements (title, footer, total, header) + this._headerFs = fs; + this._titleStyle = `font-size: ${fsUi + 3}px;`; + this._subtitleStyle = `font-size: ${fsUi}px;`; + this._totalStyle = `font-size: ${fsUi}px;`; + this._footerStyle = `font-size: ${Math.max(8, fsUi - 1)}px;`; + } + + _onStyleChanged() { + this._applyStyle(); + if (this._titleLabel) this._titleLabel.set_style(this._titleStyle); + if (this._subtitleLabel) this._subtitleLabel.set_style(this._subtitleStyle); + if (this._footerTime) this._footerTime.set_style(this._footerStyle); + if (this._footerRight) this._footerRight.set_style(this._footerStyle); + this._refresh(); + } + + // ------------------------------------------------------------------ Soup + + _initSoup() { + if (SOUP_VERSION === 3) { + this._session = new Soup.Session(); + this._session.add_feature(new Soup.CookieJar()); + } else { + this._session = new Soup.SessionAsync(); + this._session.add_feature(new Soup.CookieJar()); + } + } + + _soupGet(url, callback) { + if (SOUP_VERSION === 3) { + let msg = Soup.Message.new('GET', url); + this._session.send_and_read_async(msg, GLib.PRIORITY_DEFAULT, null, (session, result) => { + try { + let bytes = session.send_and_read_finish(result); + let status = msg.get_status(); + let body = bytes ? new TextDecoder().decode(bytes.get_data()) : ''; + callback(status, body); + } catch(e) { + callback(0, ''); + } + }); + } else { + let msg = Soup.Message.new('GET', url); + this._session.queue_message(msg, (session, response) => { + let status = response.status_code; + let body = response.response_body ? response.response_body.data : ''; + callback(status, body); + }); + } + } + + // ------------------------------------------------------------------ UI + + _buildUI() { + let outer = new St.BoxLayout({ vertical: true, style_class: 'trading-desklet' }); + this._outer = outer; + this.setContent(outer); + + // --- Logo Header --- + let headerRow = new St.BoxLayout({ style_class: 'desklet-header-row' }); + + // Logo + let iconPath = this._metadata.path + '/icon.png'; + try { + let gicon = Gio.icon_new_for_string(iconPath); + let logo = new St.Icon({ gicon: gicon, icon_size: 42, style_class: 'desklet-logo' }); + headerRow.add_child(logo); + } catch(_) {} + + // Title + let titleBox = new St.BoxLayout({ vertical: true, style_class: 'desklet-title-box' }); + this._titleLabel = new St.Label({ text: APP_NAME, style_class: 'desklet-title' }); + this._subtitleLabel = new St.Label({ text: _("Open Bitunix Futures"), style_class: 'desklet-subtitle' }); + titleBox.add_child(this._titleLabel); + titleBox.add_child(this._subtitleLabel); + headerRow.add_child(titleBox); + outer.add_child(headerRow); + + // Separator + outer.add_child(new St.Label({ text: '\u2500'.repeat(44), style_class: 'separator' })); + + // --- Content area (positions / status) --- + this._content = new St.BoxLayout({ vertical: true, style_class: 'desklet-content' }); + outer.add_child(this._content); + + // --- Footer --- + outer.add_child(new St.Label({ text: '\u2500'.repeat(44), style_class: 'separator' })); + let footer = new St.BoxLayout({ style_class: 'desklet-footer' }); + this._footerTime = new St.Label({ text: '', style_class: 'footer-time' }); + this._footerRight = new St.Label({ text: '', style_class: 'footer-right' }); + this._updateFooterRight(); + footer.add_child(this._footerTime); + let spacer = new St.Widget({ x_expand: true }); + footer.add_child(spacer); + footer.add_child(this._footerRight); + outer.add_child(footer); + + this._showStatus(_("Connecting...")); + } + + // ------------------------------------------------------------------ Polling + + _startPolling() { + this._stopPolling(); + this._refresh(); + this._timeoutId = Mainloop.timeout_add_seconds(this.refreshInterval, () => { + this._refresh(); + return GLib.SOURCE_CONTINUE; + }); + } + + _stopPolling() { + if (this._timeoutId) { + Mainloop.source_remove(this._timeoutId); + this._timeoutId = null; + } + } + + _refresh() { + if (this._cookieAcquired) { + this._fetchPositions(); + } else { + this._acquireCookie(); + } + } + + _acquireCookie() { + this._showStatus(_("Connecting to") + " " + this.serverHost + "..."); + this._soupGet(this._baseUrl + '/', (status, _body) => { + if (status > 0 && status < 500) { + this._cookieAcquired = true; + this._fetchPositions(); + } else { + this._showStatus(_("Server unreachable") + "\n" + this.serverHost + ":" + this.serverPort); + } + }); + } + + _fetchPositions() { + this._soupGet(this._baseUrl + '/api/bitunix/open-positions', (status, body) => { + if (status === 401) { + this._cookieAcquired = false; + this._acquireCookie(); + return; + } + if (status !== 200) { + this._showStatus(_("Server unreachable") + "\n" + this.serverHost + ":" + this.serverPort); + return; + } + try { + let result = JSON.parse(body); + let positions = result.positions || []; + this._renderPositions(positions); + this._updateFooterTime(); + } catch(e) { + this._showStatus(_("Parse error:") + "\n" + e.message); + } + }); + } + + _updateFooterTime() { + let now = new Date(); + let hh = String(now.getHours()).padStart(2, '0'); + let mm = String(now.getMinutes()).padStart(2, '0'); + let ss = String(now.getSeconds()).padStart(2, '0'); + if (this._footerTime) { + this._footerTime.set_text(_("Updated:") + ` ${hh}:${mm}:${ss}`); + } + } + + _updateFooterRight() { + if (!this._footerRight) return; + let text = `v${APP_VERSION}`; + if (this.showGithubLink !== false) { + text += ` \u2022 github.com/Mouses007/Crypto-Trading-Journal`; + } + this._footerRight.set_text(text); + } + + _onFooterChanged() { + this._updateFooterRight(); + } + + // ------------------------------------------------------------------ Render + + _renderPositions(positions) { + this._content.destroy_all_children(); + + if (positions.length === 0) { + this._content.add_child( + new St.Label({ text: _("No open positions"), style_class: 'no-positions-label' }) + ); + return; + } + + // Total PnL + let totalPnl = positions.reduce((sum, p) => sum + (parseFloat(p.unrealizedPNL) || 0), 0); + let totalClass = totalPnl >= 0 ? 'pnl-profit' : 'pnl-loss'; + let totalSign = totalPnl >= 0 ? '+' : ''; + let totalRow = new St.BoxLayout({ style_class: 'total-row' }); + let posWord = positions.length === 1 ? _("position") : _("positions"); + let totalLbl = new St.Label({ + text: `${_("Total:")} ${totalSign}${totalPnl.toFixed(2)} USDT (${positions.length} ${posWord})`, + style_class: 'total-label ' + totalClass + }); + if (this._totalStyle) totalLbl.set_style(this._totalStyle); + totalRow.add_child(totalLbl); + this._content.add_child(totalRow); + this._content.add_child(new St.Label({ text: '\u2500'.repeat(44), style_class: 'separator-thin' })); + + // Header row — same column widths as position rows + let headerCells = [ + { text: _("Symbol"), width: this._colWidths.symbol }, + { text: _("Side"), width: this._colWidths.side }, + ]; + if (this.showLeverage) headerCells.push({ text: _("Lvg"), width: this._colWidths.leverage }); + headerCells.push({ text: _("Entry"), width: this._colWidths.price }); + if (this.showMarkPrice) headerCells.push({ text: _("Mark"), width: this._colWidths.price }); + headerCells.push({ text: _("unr. PnL"), width: this._colWidths.pnl }); + this._content.add_child(this._makeHeaderRow(headerCells)); + + for (let pos of positions) { + this._content.add_child(this._makePositionRow(pos)); + } + } + + _makePositionRow(pos) { + let pnl = parseFloat(pos.unrealizedPNL) || 0; + let isProfit = pnl >= 0; + let pnlClass = isProfit ? 'pnl-profit' : 'pnl-loss'; + let pnlText = (isProfit ? '+' : '') + pnl.toFixed(2) + ' USDT'; + let sideLower = (pos.side || '').toLowerCase(); + let sideClass = (sideLower === 'long' || sideLower === 'buy') ? 'side-long' : 'side-short'; + + let markPrice = pos.markPrice; + if (!markPrice && pos.bitunixData) { + try { + let bd = typeof pos.bitunixData === 'string' ? JSON.parse(pos.bitunixData) : pos.bitunixData; + markPrice = bd.markPrice || bd.liqPrice || null; + } catch(_) {} + } + + let fmt = (v) => { + if (!v) return '-'; + let n = parseFloat(v); + if (isNaN(n)) return '-'; + return n >= 1000 ? n.toLocaleString('en-US', { maximumFractionDigits: 2 }) + : n >= 1 ? n.toFixed(4) + : n.toFixed(6); + }; + + let cw = this._colWidths; + let cells = [ + { text: pos.symbol || '-', cls: 'symbol-cell', width: cw.symbol }, + { text: pos.side || '-', cls: 'side-cell ' + sideClass, width: cw.side }, + ]; + if (this.showLeverage) cells.push({ text: (pos.leverage ? pos.leverage + 'x' : '-'), cls: 'leverage-cell', width: cw.leverage }); + cells.push({ text: fmt(pos.entryPrice), cls: 'price-cell', width: cw.price }); + if (this.showMarkPrice) cells.push({ text: fmt(markPrice), cls: 'price-cell', width: cw.price }); + cells.push({ text: pnlText, cls: 'pnl-cell ' + pnlClass, width: cw.pnl }); + + let fs = this._posFs || 12; + let row = new St.BoxLayout({ style_class: 'position-row' }); + for (let c of cells) { + let lbl = new St.Label({ text: c.text, style_class: c.cls }); + lbl.set_style(`font-size: ${fs}px; min-width: ${c.width}px;`); + row.add_child(lbl); + } + return row; + } + + _makeHeaderRow(cells) { + let fs = this._headerFs || 12; + let row = new St.BoxLayout({ style_class: 'header-row' }); + for (let c of cells) { + let lbl = new St.Label({ text: c.text, style_class: 'header-cell' }); + lbl.set_style(`font-size: ${fs}px; min-width: ${c.width}px;`); + row.add_child(lbl); + } + return row; + } + + _showStatus(text) { + this._content.destroy_all_children(); + this._content.add_child(new St.Label({ text, style_class: 'status-label' })); + } + + // ------------------------------------------------------------------ Lifecycle + + _onSettingChanged() { + this._cookieAcquired = false; + this._startPolling(); + } + + on_desklet_removed() { + this._stopPolling(); + } +} + +function main(metadata, desklet_id) { + return new TradingPositionsDesklet(metadata, desklet_id); +} diff --git a/trading-positions@trading-journal/files/trading-positions@trading-journal/icon.png b/trading-positions@trading-journal/files/trading-positions@trading-journal/icon.png new file mode 100644 index 000000000..6da23f009 Binary files /dev/null and b/trading-positions@trading-journal/files/trading-positions@trading-journal/icon.png differ diff --git a/trading-positions@trading-journal/files/trading-positions@trading-journal/metadata.json b/trading-positions@trading-journal/files/trading-positions@trading-journal/metadata.json new file mode 100644 index 000000000..dd64bd1d9 --- /dev/null +++ b/trading-positions@trading-journal/files/trading-positions@trading-journal/metadata.json @@ -0,0 +1,8 @@ +{ + "uuid": "trading-positions@trading-journal", + "name": "Trading Positions", + "description": "Zeigt offene Positionen aus dem Crypto Trading Journal", + "version": "1.0.0", + "max-instances": 1, + "cinnamon-version": ["5.0", "5.2", "5.4", "5.6", "5.8", "6.0", "6.2", "6.4"] +} diff --git a/trading-positions@trading-journal/files/trading-positions@trading-journal/po/de.po b/trading-positions@trading-journal/files/trading-positions@trading-journal/po/de.po new file mode 100644 index 000000000..ea8ad6552 --- /dev/null +++ b/trading-positions@trading-journal/files/trading-positions@trading-journal/po/de.po @@ -0,0 +1,120 @@ +# German translation for trading-positions@trading-journal +# Copyright (C) 2026 Mouses007 +# This file is distributed under the GPL-3.0 license. +# +msgid "" +msgstr "" +"Project-Id-Version: trading-positions@trading-journal\n" +"Report-Msgid-Bugs-To: https://github.com/linuxmint/cinnamon-spices-desklets/issues\n" +"POT-Creation-Date: 2026-03-23 12:00+0100\n" +"PO-Revision-Date: 2026-03-23 12:00+0100\n" +"Last-Translator: Mouses007\n" +"Language-Team: German\n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: desklet.js +msgid "Open Bitunix Futures" +msgstr "Offene Bitunix Futures" + +#: desklet.js +msgid "Connecting..." +msgstr "Verbinde..." + +#: desklet.js +msgid "Connecting to" +msgstr "Verbinde mit" + +#: desklet.js +msgid "Server unreachable" +msgstr "Server nicht erreichbar" + +#: desklet.js +msgid "Parse error:" +msgstr "Fehler beim Parsen:" + +#: desklet.js +msgid "Updated:" +msgstr "Aktualisiert:" + +#: desklet.js +msgid "No open positions" +msgstr "Keine offenen Positionen" + +#: desklet.js +msgid "Total:" +msgstr "Gesamt:" + +#: desklet.js +msgid "position" +msgstr "Position" + +#: desklet.js +msgid "positions" +msgstr "Positionen" + +#: desklet.js +msgid "Symbol" +msgstr "Symbol" + +#: desklet.js +msgid "Side" +msgstr "Seite" + +#: desklet.js +msgid "Lvg" +msgstr "Hebel" + +#: desklet.js +msgid "Entry" +msgstr "Einstieg" + +#: desklet.js +msgid "Mark" +msgstr "Mark" + +#: desklet.js +msgid "unr. PnL" +msgstr "unr. PnL" + +#. settings-schema.json +msgid "This desklet requires a running Crypto Trading Journal.\ngithub.com/Mouses007/Crypto-Trading-Journal" +msgstr "Dieses Desklet benötigt ein laufendes Crypto Trading Journal.\ngithub.com/Mouses007/Crypto-Trading-Journal" + +msgid "Server host" +msgstr "Server-Host" + +msgid "IP or hostname, e.g. 192.168.178.100 or localhost" +msgstr "IP oder Hostname, z.B. 192.168.178.100 oder localhost" + +msgid "Server port" +msgstr "Server-Port" + +msgid "Default: 8080" +msgstr "Standard: 8080" + +msgid "Refresh interval" +msgstr "Aktualisierungsintervall" + +msgid "Show leverage" +msgstr "Hebel anzeigen" + +msgid "Show mark price" +msgstr "Mark-Preis anzeigen" + +msgid "Show GitHub link in footer" +msgstr "GitHub-Link in Fußzeile anzeigen" + +msgid "Font size (positions)" +msgstr "Schriftgröße (Positionen)" + +msgid "Font size (title, footer, total)" +msgstr "Schriftgröße (Titel, Fußzeile, Gesamt)" + +msgid "Background opacity (0 = invisible, 100 = solid)" +msgstr "Hintergrund-Transparenz (0 = unsichtbar, 100 = voll)" + +msgid "Width" +msgstr "Breite" diff --git a/trading-positions@trading-journal/files/trading-positions@trading-journal/po/trading-positions@trading-journal.pot b/trading-positions@trading-journal/files/trading-positions@trading-journal/po/trading-positions@trading-journal.pot new file mode 100644 index 000000000..f9770ce09 --- /dev/null +++ b/trading-positions@trading-journal/files/trading-positions@trading-journal/po/trading-positions@trading-journal.pot @@ -0,0 +1,120 @@ +# Trading Positions Desklet +# Copyright (C) 2026 Mouses007 +# This file is distributed under the GPL-3.0 license. +# +msgid "" +msgstr "" +"Project-Id-Version: trading-positions@trading-journal\n" +"Report-Msgid-Bugs-To: https://github.com/linuxmint/cinnamon-spices-desklets/issues\n" +"POT-Creation-Date: 2026-03-23 12:00+0100\n" +"PO-Revision-Date: \n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: desklet.js +msgid "Open Bitunix Futures" +msgstr "" + +#: desklet.js +msgid "Connecting..." +msgstr "" + +#: desklet.js +msgid "Connecting to" +msgstr "" + +#: desklet.js +msgid "Server unreachable" +msgstr "" + +#: desklet.js +msgid "Parse error:" +msgstr "" + +#: desklet.js +msgid "Updated:" +msgstr "" + +#: desklet.js +msgid "No open positions" +msgstr "" + +#: desklet.js +msgid "Total:" +msgstr "" + +#: desklet.js +msgid "position" +msgstr "" + +#: desklet.js +msgid "positions" +msgstr "" + +#: desklet.js +msgid "Symbol" +msgstr "" + +#: desklet.js +msgid "Side" +msgstr "" + +#: desklet.js +msgid "Lvg" +msgstr "" + +#: desklet.js +msgid "Entry" +msgstr "" + +#: desklet.js +msgid "Mark" +msgstr "" + +#: desklet.js +msgid "unr. PnL" +msgstr "" + +#. settings-schema.json +msgid "This desklet requires a running Crypto Trading Journal.\ngithub.com/Mouses007/Crypto-Trading-Journal" +msgstr "" + +msgid "Server host" +msgstr "" + +msgid "IP or hostname, e.g. 192.168.178.100 or localhost" +msgstr "" + +msgid "Server port" +msgstr "" + +msgid "Default: 8080" +msgstr "" + +msgid "Refresh interval" +msgstr "" + +msgid "Show leverage" +msgstr "" + +msgid "Show mark price" +msgstr "" + +msgid "Show GitHub link in footer" +msgstr "" + +msgid "Font size (positions)" +msgstr "" + +msgid "Font size (title, footer, total)" +msgstr "" + +msgid "Background opacity (0 = invisible, 100 = solid)" +msgstr "" + +msgid "Width" +msgstr "" diff --git a/trading-positions@trading-journal/files/trading-positions@trading-journal/settings-schema.json b/trading-positions@trading-journal/files/trading-positions@trading-journal/settings-schema.json new file mode 100644 index 000000000..7bb73cd42 --- /dev/null +++ b/trading-positions@trading-journal/files/trading-positions@trading-journal/settings-schema.json @@ -0,0 +1,82 @@ +{ + "info-header": { + "type": "label", + "description": "This desklet requires a running Crypto Trading Journal.\ngithub.com/Mouses007/Crypto-Trading-Journal" + }, + "server-host": { + "type": "entry", + "default": "localhost", + "description": "Server host", + "tooltip": "IP or hostname, e.g. 192.168.178.100 or localhost" + }, + "server-port": { + "type": "spinbutton", + "default": 8080, + "min": 1024, + "max": 65535, + "step": 1, + "units": "", + "description": "Server port", + "tooltip": "Default: 8080" + }, + "refresh-interval": { + "type": "spinbutton", + "default": 30, + "min": 5, + "max": 300, + "step": 5, + "units": "seconds", + "description": "Refresh interval" + }, + "show-leverage": { + "type": "checkbox", + "default": true, + "description": "Show leverage" + }, + "show-mark-price": { + "type": "checkbox", + "default": true, + "description": "Show mark price" + }, + "show-github-link": { + "type": "checkbox", + "default": true, + "description": "Show GitHub link in footer" + }, + "font-size": { + "type": "spinbutton", + "default": 12, + "min": 9, + "max": 18, + "step": 1, + "units": "px", + "description": "Font size (positions)" + }, + "font-size-ui": { + "type": "spinbutton", + "default": 11, + "min": 8, + "max": 16, + "step": 1, + "units": "px", + "description": "Font size (title, footer, total)" + }, + "bg-opacity": { + "type": "spinbutton", + "default": 88, + "min": 0, + "max": 100, + "step": 5, + "units": "%", + "description": "Background opacity (0 = invisible, 100 = solid)" + }, + "min-width": { + "type": "spinbutton", + "default": 400, + "min": 300, + "max": 800, + "step": 20, + "units": "px", + "description": "Width" + } +} diff --git a/trading-positions@trading-journal/files/trading-positions@trading-journal/stylesheet.css b/trading-positions@trading-journal/files/trading-positions@trading-journal/stylesheet.css new file mode 100644 index 000000000..f6796dcd0 --- /dev/null +++ b/trading-positions@trading-journal/files/trading-positions@trading-journal/stylesheet.css @@ -0,0 +1,111 @@ +/* Trading Positions Desklet Stylesheet */ + +.trading-desklet { + padding: 10px 12px 8px 12px; + spacing: 3px; + min-width: 400px; + background-color: rgba(15, 20, 28, 0.88); + border-radius: 8px; + border: 1px solid rgba(255,255,255,0.12); +} + +/* ---- Header ---- */ +.desklet-header-row { + spacing: 10px; + padding-bottom: 4px; +} +.desklet-logo { + icon-size: 32px; +} +.desklet-title-box { + spacing: 0px; +} +.desklet-title { + color: #ffffff; + font-weight: bold; +} +.desklet-subtitle { + color: #8aaac0; +} + +/* ---- Separatoren ---- */ +.separator { + color: #2a3a4a; + font-size: 9px; + padding: 1px 0; +} +.separator-thin { + color: #243040; + font-size: 9px; +} + +/* ---- Content-Bereich ---- */ +.desklet-content { + spacing: 2px; + padding: 4px 0; +} + +/* ---- Status / Empty ---- */ +.status-label, +.no-positions-label { + color: #aabccc; + font-size: 12px; + padding: 6px 0; +} + +/* ---- Gesamt-PnL ---- */ +.total-row { + padding: 2px 0; +} +.total-label { + font-weight: bold; +} + +/* ---- Positions-Header ---- */ +.header-row { + spacing: 0px; + padding: 2px 0 1px 0; +} +.header-cell { + color: #7a9ab8; + font-weight: bold; +} + +/* ---- Positions-Zeilen ---- */ +.position-row { + spacing: 0px; + padding: 2px 0; +} + +.symbol-cell { + color: #ffffff; + font-weight: bold; +} +.side-cell { + font-weight: bold; +} +.side-long { color: #2ecc71; } +.side-short { color: #e74c3c; } + +.leverage-cell { + color: #c0d0e0; +} +.price-cell { + color: #d0e0f0; +} +.pnl-cell { + font-weight: bold; +} +.pnl-profit { color: #2ecc71; } +.pnl-loss { color: #e74c3c; } + +/* ---- Fußzeile ---- */ +.desklet-footer { + padding-top: 3px; +} +.footer-time { + color: #7a9ab8; +} +.footer-right { + color: #6a8aaa; +} diff --git a/trading-positions@trading-journal/info.json b/trading-positions@trading-journal/info.json new file mode 100644 index 000000000..e88a4718e --- /dev/null +++ b/trading-positions@trading-journal/info.json @@ -0,0 +1,8 @@ +{ + "author": "Mouses007", + "name": "Trading Positions", + "description": "Desktop widget for Crypto Trading Journal. Shows open Bitunix futures positions with real-time PnL updates. Requires a running Crypto Trading Journal instance (github.com/Mouses007/Crypto-Trading-Journal).", + "version": "1.0.0", + "website": "https://github.com/Mouses007/Crypto-Trading-Journal", + "comments": "Displays open trading positions fetched from the Crypto Trading Journal API. Configurable refresh interval, font sizes, and appearance." +} diff --git a/trading-positions@trading-journal/screenshot.png b/trading-positions@trading-journal/screenshot.png new file mode 100644 index 000000000..5370f0107 Binary files /dev/null and b/trading-positions@trading-journal/screenshot.png differ