Skip to content

Instantly share code, notes, and snippets.

@iCodeForBananas
Last active August 2, 2023 16:34
Show Gist options
  • Save iCodeForBananas/ec0513e1e61aab5abb40a275b0e95bcc to your computer and use it in GitHub Desktop.
Save iCodeForBananas/ec0513e1e61aab5abb40a275b0e95bcc to your computer and use it in GitHub Desktop.
paperTradingOS.js
// ==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