Last active
August 2, 2023 16:34
-
-
Save iCodeForBananas/ec0513e1e61aab5abb40a275b0e95bcc to your computer and use it in GitHub Desktop.
paperTradingOS.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name OS Paper Trading | |
// @version 15 | |
// @description Lets you trade off of replay mode so that you can keep track of practice sessions. | |
// @match https://app.oneoption.com/option-stalker/chart-dev/* | |
// @icon https://www.google.com/s2/favicons?sz=64&domain=oneoption.com | |
// @grant none | |
// ==/UserScript== | |
(function () { | |
"use strict"; | |
const showTradingPanel = () => { | |
document.querySelector('#trading-panel').classList.toggle('d-none'); | |
} | |
class PracticeTrading { | |
tradeHistory = []; | |
startingNetValue = 0; | |
init() { | |
$("body").append(` | |
<div class="d-none border-top bg-light text-center" id="trading-panel" > | |
<div id="stats" class="bg-lighter"> | |
<div class="row fw-bold border-bottom"> | |
<div class="col" title="Win rate (win count / all trades)">%</div> | |
<div class="col" title="Profit factor (gross profit / gross losses)">PF</div> | |
<div class="col" title="Number of trades">#</div> | |
</div> | |
<div class="row border-bottom"> | |
<div class="col" id="practice-trading-win-rate">%</div> | |
<div class="col" id="practice-trading-profit-factor">PF</div> | |
<div class="col" id="practice-trading-trade-count">#</div> | |
</div> | |
</div> | |
<div class="table-responsive fs-xs"> | |
<table id="practice-trading-table" class="table table-striped"> | |
<thead> | |
<tr> | |
<th>Side</th> | |
<th>Symbol</th> | |
<th>Entry</th> | |
<th>Exit</th> | |
<th>Unrealized</th> | |
<th>Realized</th> | |
</tr> | |
</thead> | |
<tbody> | |
</tbody> | |
</table> | |
</div> | |
<div id="trading-button-container" class="py-2 border-top"> | |
<div class="btn-group w-100" role="group"> | |
<button id="practice-trading-reset-action" type="button" class="btn oo-btn-faded-outline part-round-left">Reset</button> | |
<button id="practice-trading-buy-action" type="button" class="btn btn-success">Buy</button> | |
<button id="practice-trading-sell-action" type="button" class="btn btn-danger">Sell</button> | |
<button onclick="showTradingPanel()" type="button" class="btn oo-btn-faded-outline part-round-right">Close</button> | |
</div> | |
</div> | |
</div> | |
`); | |
try { | |
this.tradeHistory = window.localStorage.getItem("tradeHistory") | |
? JSON.parse(window.localStorage.getItem("tradeHistory")) | |
: []; | |
} catch (e) { | |
this.tradeHistory = []; | |
} | |
this.renderTradeHistory(); | |
this.calculateStats(); | |
$("#practice-trading-buy-action").on("click", this.handleBuy.bind(this)); | |
$("#practice-trading-sell-action").on("click", this.handleSell.bind(this)); | |
$("#practice-trading-reset-action").on("click", this.resetTradeHistory.bind(this)); | |
$(".js-rootresizer__contents.layout-with-border-radius").css("width", "calc(100% - 286px)"); | |
window.dispatchEvent(new Event("resize")); | |
} | |
getPrice() { | |
return window.OSChart.seriesData.price[window.OSChart.seriesData.price.length - 1].close; | |
} | |
getSymbol() { | |
return window.OSChart.symbol; | |
} | |
calculateStats() { | |
const wins = this.tradeHistory.filter((trade) => { | |
return ( | |
(trade.realized && trade.direction < 0 && trade.entryPrice > trade.exitPrice) || | |
(trade.realized && trade.direction > 0 && trade.exitPrice > trade.entryPrice) | |
); | |
}).length; | |
const winRateDecimal = parseInt((wins / this.tradeHistory.length) * 100); | |
const winRate = this.tradeHistory.length > 0 && wins > 0 ? String(winRateDecimal) + "%" : "--"; | |
let grossWinAmount = 0; | |
let grossLossAmount = 0; | |
this.tradeHistory.forEach((trade) => { | |
let tradeProfit = trade.realized; | |
if (tradeProfit < 0) { | |
grossLossAmount += tradeProfit; | |
} | |
if (tradeProfit > 0) { | |
grossWinAmount += tradeProfit; | |
} | |
}); | |
grossLossAmount = grossLossAmount === 0 ? 0 : grossLossAmount; | |
const profitFactor = | |
this.tradeHistory.length > 0 && grossLossAmount !== 0 | |
? Math.abs(parseFloat(grossWinAmount / grossLossAmount).toFixed(2)) | |
: "--"; | |
const avgLoss = | |
this.tradeHistory.length - wins > 0 && this.tradeHistory.length - wins > 0 | |
? grossLossAmount / (this.tradeHistory.length - wins) | |
: 0; | |
const avgWin = this.tradeHistory.length > 0 && wins > 0 ? grossWinAmount / wins : 0; | |
const profitRatio = | |
this.tradeHistory.length > 0 && avgLoss !== 0 | |
? Math.abs(parseFloat(avgWin) / parseFloat(avgLoss)).toFixed(2) | |
: "--"; | |
const tradeCount = this.tradeHistory.length > 0 ? this.tradeHistory.length : "--"; | |
const netValue = this.startingNetValue + grossLossAmount + grossWinAmount; | |
$("#practice-trading-avg-win").text(avgWin.toFixed(2)); | |
$("#practice-trading-avg-loss").text(avgLoss.toFixed(2)); | |
$("#practice-trading-trade-count").text(tradeCount); | |
$("#practice-trading-win-rate").text(winRate); | |
$("#practice-trading-profit-factor").text(profitFactor); | |
$("#practice-trading-profit-ratio").text(profitRatio); | |
$("#practice-trading-net-value").text(netValue.toFixed(2)); | |
} | |
saveTradeHistory() { | |
try { | |
window.localStorage.setItem("tradeHistory", JSON.stringify(this.tradeHistory)); | |
} catch (e) { | |
window.localStorage.setItem("tradeHistory", JSON.stringify([])); | |
} | |
} | |
resetTradeHistory() { | |
window.localStorage.setItem("tradeHistory", "[]"); | |
this.tradeHistory = []; | |
this.renderTradeHistory(); | |
this.calculateStats(); | |
} | |
// buy the ask | |
handleBuy(event, profitOverride) { | |
const price = this.getPrice(); | |
if (!price) { | |
console.error("Unable to get ask price."); | |
return; | |
} | |
const activeTradeIndex = this.tradeHistory.findIndex((trade) => { | |
return trade.exitPrice === null && trade.symbol === this.getSymbol(); | |
}); | |
if (activeTradeIndex === -1) { | |
this.tradeHistory.push({ | |
symbol: this.getSymbol(), | |
id: Date.now(), | |
barCount: 0, | |
direction: 1, | |
entryPrice: price, | |
exitPrice: null, | |
unrealized: null, | |
realized: null, | |
}); | |
} else { | |
if (this.tradeHistory[activeTradeIndex].direction === 1) { | |
// long | |
alert(`You are already long on ${this.tradeHistory[activeTradeIndex].symbol}!`) | |
return; | |
} else { | |
// short | |
this.tradeHistory[activeTradeIndex].exitPrice = price; | |
this.tradeHistory[activeTradeIndex].realized = | |
this.tradeHistory[activeTradeIndex].entryPrice - this.tradeHistory[activeTradeIndex].exitPrice; | |
this.tradeHistory[activeTradeIndex].unrealized = null; | |
} | |
} | |
this.saveTradeHistory(); | |
this.renderTradeHistory(); | |
this.calculateStats(); | |
} | |
// sell the bid | |
handleSell(event, profitOverride) { | |
const price = this.getPrice(); | |
if (!price) { | |
console.error("Unable to get bid price."); | |
return; | |
} | |
const activeTradeIndex = this.tradeHistory.findIndex((trade) => { | |
return trade.exitPrice === null && trade.symbol === this.getSymbol(); | |
}); | |
if (activeTradeIndex === -1) { | |
this.tradeHistory.push({ | |
symbol: this.getSymbol(), | |
id: Date.now(), | |
barCount: 0, | |
direction: -1, | |
entryPrice: price, | |
exitPrice: null, | |
unrealized: null, | |
realized: null, | |
}); | |
} else { | |
if (this.tradeHistory[activeTradeIndex].direction === 1) { | |
// long | |
this.tradeHistory[activeTradeIndex].exitPrice = price; | |
this.tradeHistory[activeTradeIndex].realized = | |
this.tradeHistory[activeTradeIndex].exitPrice - this.tradeHistory[activeTradeIndex].entryPrice; | |
this.tradeHistory[activeTradeIndex].unrealized = null; | |
} else { | |
// short | |
alert(`You are already short on ${this.tradeHistory[activeTradeIndex].symbol}!`) | |
return; | |
} | |
} | |
this.saveTradeHistory(); | |
this.renderTradeHistory(); | |
this.calculateStats(); | |
} | |
renderTradeHistory() { | |
$(".practice-trading-trade-history-row").remove(); | |
let tradeHTML = ``; | |
let tradeObj = window.localStorage.getItem("tradeHistory") ? | |
JSON.parse(window.localStorage.getItem("tradeHistory")) : | |
[]; | |
tradeObj.sort((a, b) => a.id - b.id); | |
tradeHTML += tradeObj.reverse().map((trade) => { | |
return ` | |
<tr class="practice-trading-trade-history-row" data-symbol="${trade.symbol}"> | |
<td> | |
${ | |
trade.direction === 1 | |
? `<span class="text-success">Long</span>` | |
: `<span class="text-danger">Short</span>` | |
} | |
</td> | |
<td> | |
${trade.symbol} | |
</td> | |
<td class="entryPrice"> | |
${trade.entryPrice ? parseFloat(trade.entryPrice).toFixed(2) : "--"} | |
</td> | |
<td class="exitPrice"> | |
${trade.exitPrice ? parseFloat(trade.exitPrice).toFixed(2) : "--"} | |
</td> | |
<td class="unrealizedPnL"> | |
${trade.unrealized === null ? "--" : ""} | |
${trade.unrealized < 0 ? "<span class='text-danger'>" + trade.unrealized.toFixed(2) + "</span>" : ""} | |
${trade.unrealized > 0 ? "<span class='text-success'>+" + trade.unrealized.toFixed(2) + "</span>" : ""} | |
${trade.unrealized === 0 ? "<span>" + trade.unrealized.toFixed(2) + "</span>" : ""} | |
</td> | |
<td class="realizedPnL"> | |
${trade.realized === null ? "--" : ""} | |
${trade.realized < 0 ? "<span class='text-danger'>" + trade.realized.toFixed(2) + "</span>" : ""} | |
${trade.realized > 0 ? "<span class='text-success'>+" + trade.realized.toFixed(2) + "</span>" : ""} | |
${trade.realized === 0 ? "<span>" + trade.realized.toFixed(2) + "</span>" : ""} | |
</td> | |
</tr>`; | |
}); | |
$("#practice-trading-table tbody").append(tradeHTML); | |
} | |
} | |
window.PracticeTradingInstance = new PracticeTrading(); | |
window.PracticeTradingInstance.init(); | |
})(); | |
function showTradingPanel () { | |
document.querySelector('#trading-panel').classList.toggle('d-none') | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment