diff --git a/src/router/index.js b/src/router/index.js
index 9fe76e3..f1be9f6 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -151,6 +151,16 @@ const router = createRouter({
component: () =>
import('../views/AddScreenshot.vue')
},
+ {
+ path: '/addExcursions',
+ name: 'addExcursions',
+ meta: {
+ title: "Add Excursions",
+ layout: DashboardLayout
+ },
+ component: () =>
+ import('../views/AddExcursions.vue')
+ },
{
path: '/settings',
name: 'settings',
diff --git a/src/stores/globals.js b/src/stores/globals.js
index 04d6db6..a9f9801 100644
--- a/src/stores/globals.js
+++ b/src/stores/globals.js
@@ -94,6 +94,7 @@ export const tradeId = ref()
export const existingImports = reactive([])
export const existingTradesArray = reactive([])
export const gotExistingTradesArray = ref(false)
+export const marketCloseTime = ref("16:00:00")
export const futureContractsJson = ref(
[
@@ -2799,6 +2800,13 @@ export const tradovateTiers = reactive([{
])
export const selectedTradovateTier = ref()
+
+/**************************************
+* ADD EXCURSIONS
+**************************************/
+export const daysBack = ref(30)
+export const daysMargin = ref(2)
+
/**************************************
* CHARTS
**************************************/
diff --git a/src/utils/addTrades.js b/src/utils/addTrades.js
index 0b4c515..ae3613e 100644
--- a/src/utils/addTrades.js
+++ b/src/utils/addTrades.js
@@ -1,4 +1,4 @@
-import { filteredTradesTrades, blotter, pAndL, tradeExcursionId, spinnerLoadingPage, currentUser, selectedBroker, tradesData, timeZoneTrade, uploadMfePrices, executions, tradeId, existingImports, trades, gotExistingTradesArray, existingTradesArray, brokerData, selectedTradovateTier, queryLimit } from '../stores/globals.js'
+import { filteredTradesTrades, blotter, pAndL, tradeExcursionId, spinnerLoadingPage, currentUser, selectedBroker, tradesData, timeZoneTrade, uploadMfePrices, executions, tradeId, existingImports, trades, gotExistingTradesArray, existingTradesArray, brokerData, selectedTradovateTier, queryLimit, marketCloseTime } from '../stores/globals.js'
import { useBrokerHeldentrader, useBrokerInteractiveBrokers, useBrokerMetaTrader5, useBrokerTdAmeritrade, useBrokerTradeStation, useBrokerTradeZero, useTradovate, useNinjaTrader, useRithmic, useFundTraders } from './brokers.js'
import { useChartFormat, useDateTimeFormat, useDecimalsArithmetic, useInitParse, useTimeFormat } from './utils.js'
@@ -273,14 +273,14 @@ export async function useImportTrades(param1, param2, param3, param0) {
await createExecutions()
- if (currentUser.value.hasOwnProperty('apis') && (currentUser.value.apis.findIndex(obj => obj.provider === 'polygon' || obj.provider === 'databento') > -1) && uploadMfePrices.value) {
+ if (currentUser.value.hasOwnProperty('apis') && (currentUser.value.apis.findIndex(obj => obj.provider === 'polygon' || obj.provider === 'databento') > -1) && uploadMfePrices.value) {
let databentoIndex = currentUser.value.apis.findIndex(obj => obj.provider === "databento")
let polygonIndex = currentUser.value.apis.findIndex(obj => obj.provider === "polygon")
- if (databentoIndex > -1 && currentUser.value.apis[databentoIndex].key !="") {
+ if (databentoIndex > -1 && currentUser.value.apis[databentoIndex].key != "") {
try {
- await getOHLCV("databento", param2);
+ await useGetOHLCV("databento", param2);
} catch (error) {
if (param2 != "api") {
alert("Error getting OHLCV (" + error + ")")
@@ -288,9 +288,9 @@ export async function useImportTrades(param1, param2, param3, param0) {
reject("Error getting OHLCV (" + error + ")")
return; // stop the function execution
}
- } else if (polygonIndex > -1 && currentUser.value.apis[polygonIndex].key !="") {
+ } else if (polygonIndex > -1 && currentUser.value.apis[polygonIndex].key != "") {
try {
- await getOHLCV("polygon", param2);
+ await useGetOHLCV("polygon", param2);
} catch (error) {
if (param2 != "api") {
alert("Error getting OHLCV (" + error + ")")
@@ -546,12 +546,20 @@ export const useCreateOHLCV = (param, param2) => {
})
}
-const getOHLCV = (param, param2) => {
+export const useGetOHLCV = (param, param2, param3, param4, param5) => { //param=databento/polygon, param2=api or other, param3+ is used for AddExcursions: param3=tradedSymbols, param4=tradedStartDate, param5=tradedEndDate
return new Promise(async (resolve, reject) => {
console.log("\nGETTING OHLCV from " + param)
+
+ if (param3) tradedSymbols = param3
+ if (param4) tradedStartDate = param4
+ if (param5) tradedEndDate = param5
+
//spinnerLoadingPageText.value = "Getting OHLCV"
console.log(" Traded Symbols " + JSON.stringify(tradedSymbols))
ohlcv.length = 0 // reinitialize, for API
+
+
+
const asyncLoop = async () => {
for (let i = 0; i < tradedSymbols.length; i++) { // I think that async needs to be for instead of foreach
let temp = {}
@@ -637,7 +645,7 @@ const getOHLCV = (param, param2) => {
axios.post('/api/databento', data)
.then(async (response) => {
await useCreateOHLCV(response.data, temp)
- resolve()
+ resolve(ohlcv)
})
.catch((error) => {
console.log(" -> Error in databento response " + error)
@@ -686,7 +694,7 @@ const getOHLCV = (param, param2) => {
temp.ohlcv = response.data.results
ohlcv.push(temp)
//console.log(" -> ohlcv " + JSON.stringify(ohlcv))
- resolve()
+ resolve(ohlcv)
})
.catch((error) => {
@@ -705,6 +713,178 @@ const getOHLCV = (param, param2) => {
})
}
+export const useGetMFEPrices = (tempExec, initEntryTime, initEntryPrice, trde, ohlcvParam) => {
+ return new Promise(async (resolve, reject) => {
+ console.log(" --> Getting MFE Price")
+
+ if (ohlcvParam) {
+ ohlcv = ohlcvParam //case when we add MFE from daily
+ }
+
+ let ohlcvSymbol = ohlcv[ohlcv.findIndex(f => f.symbol == tempExec.symbol)].ohlcv
+ //todo exclude if trade in same minute timeframe
+
+ //console.log(" ohlcvSymbol " + JSON.stringify(ohlcvSymbol))
+
+ if (ohlcvSymbol != undefined) {
+ //findIndex gets the first value. So, for entry, if equal, we take next candle. For exit, if equal, we use that candle
+ let tempStartIndex = ohlcvSymbol.findIndex(n => n.t >= initEntryTime * 1000)
+ let tempEndIndex = ohlcvSymbol.findIndex(n => n.t >= trde.exitTime * 1000) //findIndex returns the first element
+ let tempStartTime = ohlcvSymbol[tempStartIndex]
+ let tempEndTime = ohlcvSymbol[tempEndIndex]
+
+ let startIndex
+ let endIndex
+ let startTime
+ let endTime
+
+ if (tempStartTime == initEntryTime) {
+ startIndex = tempStartIndex + 1
+ startTime = ohlcvSymbol[startIndex].t
+ } else {
+ startIndex = tempStartIndex
+ startTime = ohlcvSymbol[tempStartIndex].t
+ }
+
+ if (tempEndTime == trde.exitTime) {
+ endIndex = tempEndIndex
+ endTime = tempEndTime
+ } else {
+ endIndex = tempEndIndex - 1
+ endTime = ohlcvSymbol[tempEndIndex - 1].t
+ }
+
+ //console.log(" ----> Temp Start index " + tempStartIndex + ", temp end index " + tempEndIndex)
+ //console.log(" ----> EntryTime " + initEntryTime + " and start time " + startTime)
+ //console.log(" ----> ExitTime " + trde.exitTime + " and end time " + endTime)
+
+ //Get market close index
+ //iterate from exit time and check if same day and <= 4 hour
+ let endTimeDay = dayjs(endTime).tz(timeZoneTrade.value).get("date")
+ let endTimeMonth = dayjs(endTime).tz(timeZoneTrade.value).get("month") + 1
+ let endTimeYear = dayjs(endTime).tz(timeZoneTrade.value).get("year")
+ let endTimeDate = endTimeYear + "-" + endTimeMonth + "-" + endTimeDay + " "+ marketCloseTime.value //VERY IMPORTANT : if i want to apply US time, it needs to be in US format, that is YYYY-MM-DD
+ //console.log(" -> End time date "+endTimeDate)
+ //
+ let marketCloseDateTime = dayjs.tz(endTimeDate, timeZoneTrade.value)
+
+ //console.log(" marketCloseDateTime "+marketCloseDateTime)
+
+ let tempEndOfDayTimeIndex = ohlcvSymbol.findIndex(f => f.t >= marketCloseDateTime)
+ let endOfDayTimeIndex = tempEndOfDayTimeIndex - 1
+ //console.log(" -> End of day time index "+endOfDayTimeIndex+" and values are "+JSON.stringify(ohlcvSymbol[endOfDayTimeIndex]))
+
+ /*let timeDayOfWeek = dayjs(endTime * 1000).tz(timeZoneTrade.value).day()
+ console.log(" timeDayOfWeek "+timeDayOfWeek)
+ let timeHour = dayjs(endTime * 1000).tz(timeZoneTrade.value).get('hour')
+ let time
+ let i = tempEndIndex - 1;
+ while (i < ohlcvSymbol.length && endTimeOfWeek == timeDayOfWeek && timeHour < 17) {
+ time = ohlcvSymbol[i].t
+ timeDayOfWeek = dayjs(time * 1000).tz(timeZoneTrade.value).day()
+ timeHour = dayjs(time * 1000).tz(timeZoneTrade.value).get('hour')
+ //console.log("time: "+time+", timeDayOfWeek "+timeDayOfWeek+" and hour "+timeHour)
+ i++;
+ }
+ console.log(" -> Time "+time)
+ //let tempEndOfDayTimeIndex = ohlcvSymbol.findIndex(f => f.t >= time)
+ let endOfDayTimeIndex = tempEndOfDayTimeIndex - 1
+
+ //console.log(" -> End of day time " + endOfDayTime)
+ console.log(" -> end of day time index temp "+tempEndOfDayTimeIndex)
+ console.log(" -> end of day time index "+endOfDayTimeIndex+"+ and values are "+JSON.stringify(ohlcvSymbol[endOfDayTimeIndex]))*/
+
+ let tempMfe = {}
+ //check is same timeframe
+ if (endTime < startTime) { //entry and exit are in the same 1mn timeframe
+ console.log(" ---> Trade is in same 1mn timeframe")
+
+ tempMfe.tradeId = trde.id
+ tempMfe.dateUnix = tempExec.td
+ tempMfe.mfePrice = initEntryPrice
+ mfePrices.push(tempMfe)
+
+ } else {
+ //we get the MFE price by iterating between entry and exit and then between exit up until price hits / equals entryprice, and at the latest the endOfDayTime
+ let priceDifference
+ let mfePrice = initEntryPrice
+
+ if (trde.strategy == "long") {
+ priceDifference = trde.exitPrice - initEntryPrice
+ }
+ if (trde.strategy == "short") {
+ priceDifference = initEntryPrice - trde.exitPrice
+ }
+
+ console.log(" ---> Iterating between entry price and exit price")
+ //console.log(" mfePrice 1 "+mfePrice)
+ //console.log("priceDifference "+priceDifference)
+ //console.log("initEntryPrice "+initEntryPrice)
+ //console.log("trde.exitPrice "+trde.exitPrice)
+
+ //console.log(" ----> Start index "+startIndex+ " and end index "+endIndex)
+ for (let i = startIndex; i <= endIndex; i++) {
+ //console.log(" Symbole price "+ohlcvSymbol.h[i]+" at time "+ohlcvSymbol.t[i]+" and MFE "+mfePrice)
+ if (trde.strategy == "long" && ohlcvSymbol[i].h > trde.exitPrice && ohlcvSymbol[i].h > mfePrice) mfePrice = ohlcvSymbol[i].h
+ if (trde.strategy == "short" && ohlcvSymbol[i].l < trde.exitPrice && ohlcvSymbol[i].l < mfePrice) mfePrice = ohlcvSymbol[i].l
+
+ }
+ //console.log(" -> Price difference "+priceDifference)
+ if (initEntryPrice != trde.exitPrice && priceDifference > 0) { //case where stop or exit price loss above entryprice
+ console.log(" ---> Iterating between exit price and up until price hits / equals entry price, and at the latest until market close")
+ let i = endIndex
+ let ohlcvSymbolPrice
+ trde.strategy == "long" ? ohlcvSymbolPrice = ohlcvSymbol[endIndex].h : ohlcvSymbolPrice = ohlcvSymbol[endIndex].l
+
+ //console.log(" -> endOfDayTimeIndex "+endOfDayTimeIndex)
+
+ //we iterate until the end of trading day OR until is equal to the exitTrade time (endTime) or when the price move above or below the initEntryPrice
+ /*console.log(" i "+i)
+ console.log(" endOfDayTimeIndex "+endOfDayTimeIndex)
+ console.log(" ohlcvSymbol[i] "+ JSON.stringify(ohlcvSymbol[i]))
+ console.log(" ohlcvSymbol[endOfDayTimeIndex] "+ JSON.stringify(ohlcvSymbol[endOfDayTimeIndex]))
+ console.log(" ohlcvSymbol[i].t / 1000 "+ohlcvSymbol[i].t / 1000)
+ console.log(" endTime / 1000 "+endTime / 1000)
+ console.log(' ohlcvSymbolPrice '+ohlcvSymbolPrice)
+ console.log(" initEntryPrice "+initEntryPrice)*/
+
+ while ((trde.strategy == "long" ? ohlcvSymbolPrice > initEntryPrice : ohlcvSymbolPrice < initEntryPrice) && i <= endOfDayTimeIndex /*&& ((ohlcvSymbol[i].t / 1000) <= ((endTime / 1000)))*/) {
+ trde.strategy == "long" ? ohlcvSymbolPrice = ohlcvSymbol[i].h : ohlcvSymbolPrice = ohlcvSymbol[i].l
+ //console.log(" -> Symbol Price " + ohlcvSymbolPrice + " @ "+useDateTimeFormat(ohlcvSymbol[i].t/1000)+", init price " + initEntryPrice + " and mfe price " + mfePrice)
+ if (trde.strategy == "long" && ohlcvSymbolPrice > initEntryPrice && ohlcvSymbolPrice > mfePrice) mfePrice = ohlcvSymbolPrice
+ if (trde.strategy == "short" && ohlcvSymbolPrice < initEntryPrice && ohlcvSymbolPrice < mfePrice) mfePrice = ohlcvSymbolPrice
+ i++
+
+ }
+ }
+
+ if (trde.strategy == "long" && mfePrice < initEntryPrice) mfePrice = initEntryPrice
+ if (trde.strategy == "short" && mfePrice > initEntryPrice) mfePrice = initEntryPrice
+
+ console.log(" ----> " + trde.strategy + " stratgy with entry at " + useDateTimeFormat(initEntryTime) + " @ " + initEntryPrice + " -> exit at " + useDateTimeFormat(trde.exitTime) + " @ " + trde.exitPrice + " and MFE price " + mfePrice)
+ //if short, MFE price = if price is lower than MFE
+ //if long, MFE = if price is higher than MFE
+
+
+
+
+ //add excursion to temp2
+ trde.excursions = {}
+ trde.excursions.stopLoss = null
+ trde.excursions.maePrice = null
+ trde.excursions.mfePrice = mfePrice
+
+ tempMfe.tradeId = trde.id
+ tempMfe.dateUnix = tempExec.td
+ tempMfe.mfePrice = mfePrice
+ mfePrices.push(tempMfe)
+ }
+ } else {
+ console.log(" ---> Cannot find symbol in market data provider")
+ }
+ resolve(mfePrices)
+ })
+}
async function getOpenPositionsParse(param99, param0) {
return new Promise(async (resolve, reject) => {
@@ -1191,159 +1371,7 @@ async function createTrades() {
*****/
if (uploadMfePrices.value && ohlcv.findIndex(f => f.symbol == tempExec.symbol) != -1) {
- console.log(" --> Getting MFE Price")
- let ohlcvSymbol = ohlcv[ohlcv.findIndex(f => f.symbol == tempExec.symbol)].ohlcv
- //todo exclude if trade in same minute timeframe
-
- //console.log(" ohlcvSymbol " + JSON.stringify(ohlcvSymbol))
-
- if (ohlcvSymbol != undefined) {
- //findIndex gets the first value. So, for entry, if equal, we take next candle. For exit, if equal, we use that candle
- let tempStartIndex = ohlcvSymbol.findIndex(n => n.t >= initEntryTime * 1000)
- let tempEndIndex = ohlcvSymbol.findIndex(n => n.t >= trde.exitTime * 1000) //findIndex returns the first element
- let tempStartTime = ohlcvSymbol[tempStartIndex]
- let tempEndTime = ohlcvSymbol[tempEndIndex]
-
- let startIndex
- let endIndex
- let startTime
- let endTime
-
- if (tempStartTime == initEntryTime) {
- startIndex = tempStartIndex + 1
- startTime = ohlcvSymbol[startIndex].t
- } else {
- startIndex = tempStartIndex
- startTime = ohlcvSymbol[tempStartIndex].t
- }
-
- if (tempEndTime == trde.exitTime) {
- endIndex = tempEndIndex
- endTime = tempEndTime
- } else {
- endIndex = tempEndIndex - 1
- endTime = ohlcvSymbol[tempEndIndex - 1].t
- }
-
- //console.log(" ----> Temp Start index " + tempStartIndex + ", temp end index " + tempEndIndex)
- //console.log(" ----> EntryTime " + initEntryTime + " and start time " + startTime)
- //console.log(" ----> ExitTime " + trde.exitTime + " and end time " + endTime)
-
- //Get market close index
- //iterate from exit time and check if same day and <= 4 hour
- let endTimeDay = dayjs(endTime).tz(timeZoneTrade.value).get("date")
- let endTimeMonth = dayjs(endTime).tz(timeZoneTrade.value).get("month") + 1
- let endTimeYear = dayjs(endTime).tz(timeZoneTrade.value).get("year")
- let endTimeDate = endTimeYear + "-" + endTimeMonth + "-" + endTimeDay + " 16:00:00" //VERY IMPORTANT : if i want to apply US time, it needs to be in US format, that is YYYY-MM-DD
- //console.log(" -> End time date "+endTimeDate)
- //
- let marketCloseTime = dayjs.tz(endTimeDate, timeZoneTrade.value)
-
- //console.log(" marketCloseTime "+marketCloseTime)
-
- let tempEndOfDayTimeIndex = ohlcvSymbol.findIndex(f => f.t >= marketCloseTime)
- let endOfDayTimeIndex = tempEndOfDayTimeIndex - 1
- //console.log(" -> End of day time index "+endOfDayTimeIndex+" and values are "+JSON.stringify(ohlcvSymbol[endOfDayTimeIndex]))
-
- /*let timeDayOfWeek = dayjs(endTime * 1000).tz(timeZoneTrade.value).day()
- console.log(" timeDayOfWeek "+timeDayOfWeek)
- let timeHour = dayjs(endTime * 1000).tz(timeZoneTrade.value).get('hour')
- let time
- let i = tempEndIndex - 1;
- while (i < ohlcvSymbol.length && endTimeOfWeek == timeDayOfWeek && timeHour < 17) {
- time = ohlcvSymbol[i].t
- timeDayOfWeek = dayjs(time * 1000).tz(timeZoneTrade.value).day()
- timeHour = dayjs(time * 1000).tz(timeZoneTrade.value).get('hour')
- //console.log("time: "+time+", timeDayOfWeek "+timeDayOfWeek+" and hour "+timeHour)
- i++;
- }
- console.log(" -> Time "+time)
- //let tempEndOfDayTimeIndex = ohlcvSymbol.findIndex(f => f.t >= time)
- let endOfDayTimeIndex = tempEndOfDayTimeIndex - 1
-
- //console.log(" -> End of day time " + endOfDayTime)
- console.log(" -> end of day time index temp "+tempEndOfDayTimeIndex)
- console.log(" -> end of day time index "+endOfDayTimeIndex+"+ and values are "+JSON.stringify(ohlcvSymbol[endOfDayTimeIndex]))*/
-
- let tempMfe = {}
- //check is same timeframe
- if (endTime < startTime) { //entry and exit are in the same 1mn timeframe
- console.log(" ---> Trade is in same 1mn timeframe")
-
- tempMfe.tradeId = trde.id
- tempMfe.dateUnix = tempExec.td
- tempMfe.mfePrice = initEntryPrice
- mfePrices.push(tempMfe)
-
- } else {
- //we get the MFE price by iterating between entry and exit and then between exit up until price hits / equals entryprice, and at the latest the endOfDayTime
- let priceDifference
- let mfePrice = initEntryPrice
-
- if (trde.strategy == "long") {
- priceDifference = trde.exitPrice - initEntryPrice
- }
- if (trde.strategy == "short") {
- priceDifference = initEntryPrice - trde.exitPrice
- }
-
- console.log(" ---> Iterating between entry price and exit price")
- //console.log(" mfePrice 1 "+mfePrice)
- //console.log("priceDifference "+priceDifference)
- //console.log("initEntryPrice "+initEntryPrice)
- //console.log("trde.exitPrice "+trde.exitPrice)
-
- //console.log(" ----> Start index "+startIndex+ " and end index "+endIndex)
- for (let i = startIndex; i <= endIndex; i++) {
- //console.log(" Symbole price "+ohlcvSymbol.h[i]+" at time "+ohlcvSymbol.t[i]+" and MFE "+mfePrice)
- if (trde.strategy == "long" && ohlcvSymbol[i].h > trde.exitPrice && ohlcvSymbol[i].h > mfePrice) mfePrice = ohlcvSymbol[i].h
- if (trde.strategy == "short" && ohlcvSymbol[i].l < trde.exitPrice && ohlcvSymbol[i].l < mfePrice) mfePrice = ohlcvSymbol[i].l
-
- }
- //console.log(" -> Price difference "+priceDifference)
- if (initEntryPrice != trde.exitPrice && priceDifference > 0) { //case where stop or exit price loss above entryprice
- console.log(" ---> Iterating between exit price and up until price hits / equals entry price, and at the latest until market close")
- let i = endIndex
- let ohlcvSymbolPrice
- trde.strategy == "long" ? ohlcvSymbolPrice = ohlcvSymbol[endIndex].h : ohlcvSymbolPrice = ohlcvSymbol[endIndex].l
-
- //console.log(" -> endOfDayTimeIndex "+endOfDayTimeIndex)
-
- //we iterate until the end of trading day OR until is equal to the exitTrade time (endTime) or when the price move above or below the initEntryPrice
- while (i <= endOfDayTimeIndex && ((ohlcvSymbol[i].t/1000) <= ((endTime/1000))) && (trde.strategy == "long" ? ohlcvSymbolPrice > initEntryPrice : ohlcvSymbolPrice < initEntryPrice)) {
- trde.strategy == "long" ? ohlcvSymbolPrice = ohlcvSymbol[i].h : ohlcvSymbolPrice = ohlcvSymbol[i].l
- //console.log(" -> Symbol Price " + ohlcvSymbolPrice + " @ "+useDateTimeFormat(ohlcvSymbol[i].t/1000)+", init price " + initEntryPrice + " and mfe price " + mfePrice)
- if (trde.strategy == "long" && ohlcvSymbolPrice > initEntryPrice && ohlcvSymbolPrice > mfePrice) mfePrice = ohlcvSymbolPrice
- if (trde.strategy == "short" && ohlcvSymbolPrice < initEntryPrice && ohlcvSymbolPrice < mfePrice) mfePrice = ohlcvSymbolPrice
- i++
-
- }
- }
-
- if (trde.strategy == "long" && mfePrice < initEntryPrice) mfePrice = initEntryPrice
- if (trde.strategy == "short" && mfePrice > initEntryPrice) mfePrice = initEntryPrice
-
- console.log(" ----> " + trde.strategy + " stratgy with entry at " + useDateTimeFormat(initEntryTime) + " @ " + initEntryPrice + " -> exit at " + useDateTimeFormat(trde.exitTime) + " @ " + trde.exitPrice + " and MFE price " + mfePrice)
- //if short, MFE price = if price is lower than MFE
- //if long, MFE = if price is higher than MFE
-
-
-
-
- //add excursion to temp2
- trde.excursions = {}
- trde.excursions.stopLoss = null
- trde.excursions.maePrice = null
- trde.excursions.mfePrice = mfePrice
-
- tempMfe.tradeId = trde.id
- tempMfe.dateUnix = tempExec.td
- tempMfe.mfePrice = mfePrice
- mfePrices.push(tempMfe)
- }
- } else {
- console.log(" ---> Cannot find symbol in market data provider")
- }
+ await useGetMFEPrices(tempExec, initEntryTime, initEntryPrice, trde)
} // End MFE prices
/*****
* END GETTING MFE PRICE
@@ -1417,7 +1445,7 @@ async function createTrades() {
})
}
-async function updateMfePrices(param99, param0) {
+export const useUpdateMfePrices = async(param99, param0, param2) => {
return new Promise(async (resolve, reject) => {
console.log(" --> Updating excursion DB with MFE price")
//spinnerLoadingPageText.value = "Updating MFE prices in excursions"
@@ -2190,7 +2218,7 @@ export async function useUploadTrades(param99, param0) {
}
if (Object.keys(executions).length > 0) await uploadFunction("trades")
- if (Object.keys(executions).length > 0 && mfePrices.length > 0) await updateMfePrices(param99, param0)
+ if (Object.keys(executions).length > 0 && mfePrices.length > 0) await useUpdateMfePrices(param99, param0)
if (openPositionsParse.length > 0) {
await loopOpenPositionsParse()
}
diff --git a/src/utils/brokers.js b/src/utils/brokers.js
index fdb119f..a0754e0 100644
--- a/src/utils/brokers.js
+++ b/src/utils/brokers.js
@@ -675,7 +675,8 @@ export async function useBrokerInteractiveBrokers(param, param2) {
temp["Exec Time"] = tempEntryHour + ":" + tempEntryMinutes + ":" + tempEntrySeconds
- temp.Comm = (-Number(element.Commission)).toString()
+ let commNum = Number(element.Commission)
+ temp.Comm = (-commNum).toString()
temp.SEC = "0"
temp.TAF = "0"
temp.NSCC = "0"
@@ -683,7 +684,7 @@ export async function useBrokerInteractiveBrokers(param, param2) {
temp["ECN Remove"] = "0"
temp["ECN Add"] = "0"
temp["Gross Proceeds"] = element.Proceeds
- temp["Net Proceeds"] = element.NetCash
+ temp["Net Proceeds"] = element.Proceeds - (-commNum) // I'm not using Net Cash because on same day or sometimes with normal input, Net Cash is not / still not calculated on IBKR side. So I calculate it myself
temp["Clr Broker"] = ""
temp.Liq = ""
temp.Note = ""
diff --git a/src/utils/daily.js b/src/utils/daily.js
index 31f98bf..4fa8baa 100644
--- a/src/utils/daily.js
+++ b/src/utils/daily.js
@@ -1,8 +1,23 @@
import { excursions, queryLimit, satisfactionArray, satisfactionTradeArray, tags, selectedRange, availableTags, currentUser, tradeTags, tradeTagsDateUnix, tradeTagsId, newTradeTags, pageId, notes, tradeNote, tradeNoteDateUnix, tradeNoteId, spinnerSetups, spinnerSetupsText, availableTagsArray, tagInput, selectedTagIndex, showTagsList, tradeTagsChanged, filteredTrades, itemTradeIndex, tradeIndex, saveButton, screenshot, screenshotsPagination, screenshotsQueryLimit, diaryUpdate, diaryQueryLimit, diaryPagination } from "../stores/globals.js";
+import { daysBack } from "../stores/globals.js";
/* MODULES */
import Parse from 'parse/dist/parse.min.js'
-
+import dayjs from 'dayjs'
+import utc from 'dayjs/plugin/utc.js'
+dayjs.extend(utc)
+import isoWeek from 'dayjs/plugin/isoWeek.js'
+dayjs.extend(isoWeek)
+import timezone from 'dayjs/plugin/timezone.js'
+dayjs.extend(timezone)
+import duration from 'dayjs/plugin/duration.js'
+dayjs.extend(duration)
+import updateLocale from 'dayjs/plugin/updateLocale.js'
+dayjs.extend(updateLocale)
+import localizedFormat from 'dayjs/plugin/localizedFormat.js'
+dayjs.extend(localizedFormat)
+import customParseFormat from 'dayjs/plugin/customParseFormat.js'
+dayjs.extend(customParseFormat)
//query limit must be same as diary limit
let satisfactionPagination = 0
@@ -117,15 +132,25 @@ export const useUpdateDailySatisfaction = async (param1, param2) => { //param1 :
export async function useGetExcursions() {
return new Promise(async (resolve, reject) => {
console.log("\nGETTING EXCURSIONS")
- let startD = selectedRange.value.start
- let endD = selectedRange.value.end
+
const parseObject = Parse.Object.extend("excursions");
const query = new Parse.Query(parseObject);
query.equalTo("user", Parse.User.current());
query.ascending("order");
- query.greaterThanOrEqualTo("dateUnix", startD)
- query.lessThan("dateUnix", endD)
- query.limit(queryLimit.value); // limit to at most 10 results
+ if (pageId.value === "addExcursions") {
+ let startD = dayjs().subtract(daysBack.value, 'days').unix()
+ let endD = dayjs().unix()
+ query.greaterThanOrEqualTo("dateUnix", startD)
+ query.lessThan("dateUnix", endD)
+ query.ascending("dateUnix");
+ }
+ else {
+ let startD = selectedRange.value.start
+ let endD = selectedRange.value.end
+ query.greaterThanOrEqualTo("dateUnix", startD)
+ query.lessThan("dateUnix", endD)
+ query.limit(queryLimit.value); // limit to at most 10 results
+ }
excursions.length = 0
const results = await query.find();
results.forEach(element => {
@@ -395,7 +420,7 @@ export const useTradeTagsChange = async (param1, param2) => {
//console.log(" itemTradeIndex.value " + itemTradeIndex.value)
//console.log(" tradeIndex.value " + tradeIndex.value)
tradeTagsDateUnix.value = filteredTrades[itemTradeIndex.value].dateUnix
- console.log(" tradeIndex.value "+tradeIndex.value)
+ console.log(" tradeIndex.value " + tradeIndex.value)
if (tradeIndex.value != undefined) {
tradeTagsId.value = filteredTrades[itemTradeIndex.value].trades[tradeIndex.value].id
} else {
@@ -684,7 +709,7 @@ export async function useGetNotes() {
}
}
- console.log(" --> notes " + JSON.stringify(notes))
+ //console.log(" --> notes " + JSON.stringify(notes))
resolve()
})
diff --git a/src/utils/trades.js b/src/utils/trades.js
index 78986df..c7f8c66 100644
--- a/src/utils/trades.js
+++ b/src/utils/trades.js
@@ -1,4 +1,4 @@
-import { pageId, spinnerLoadingPage, selectedRange, selectedDateRange, filteredTrades, filteredTradesTrades, selectedPositions, selectedAccounts, pAndL, queryLimit, blotter, totals, totalsByDate, groups, profitAnalysis, timeFrame, timeZoneTrade, hasData, satisfactionArray, satisfactionTradeArray, tags, filteredTradesDaily, dailyPagination, dailyQueryLimit, endOfList, excursions, selectedTags, availableTags, selectedItem, imports } from "../stores/globals.js"
+import { pageId, spinnerLoadingPage, selectedRange, selectedDateRange, filteredTrades, filteredTradesTrades, selectedPositions, selectedAccounts, pAndL, queryLimit, blotter, totals, totalsByDate, groups, profitAnalysis, timeFrame, timeZoneTrade, hasData, satisfactionArray, satisfactionTradeArray, tags, filteredTradesDaily, dailyPagination, dailyQueryLimit, endOfList, excursions, selectedTags, availableTags, selectedItem, imports, daysBack } from "../stores/globals.js"
import { useMountDashboard, useMountDaily, useMountCalendar, useDateTimeFormat } from "./utils.js";
import { useCreateBlotter, useCreatePnL } from "./addTrades.js"
@@ -293,7 +293,15 @@ export async function useGetTrades(param) {
if (pageId.value === "imports" || param === "imports") {
query.descending("dateUnix");
query.limit(20);
- } else {
+ }
+ else if (pageId.value === "addExcursions") {
+ let startD = dayjs().subtract(daysBack.value, 'days').unix()
+ let endD = dayjs().unix()
+ query.greaterThanOrEqualTo("dateUnix", startD)
+ query.lessThan("dateUnix", endD)
+ query.ascending("dateUnix");
+ }
+ else {
let startD = selectedRange.value.start
let endD = selectedRange.value.end
//console.log("start D "+startD)
diff --git a/src/utils/utils.js b/src/utils/utils.js
index 697d09d..7eeddb2 100644
--- a/src/utils/utils.js
+++ b/src/utils/utils.js
@@ -658,7 +658,7 @@ export function useInitPopover() {
var popDel;
- document.addEventListener('click', async function (e) {
+ document.addEventListener('click', async function (e) {
if (e.target.classList.contains('popoverDelete')) {
popDel = e.target;
document.querySelectorAll('.popoverDelete').forEach(function (popDelete) {
@@ -1027,7 +1027,10 @@ export function useEditItem(param) {
}
}
-export function usePageRedirect() {
+export function usePageRedirect(param) {
+ if(param){
+ window.location.href = "/" + param
+ }
if (pageId.value == "daily") {
window.location.href = "/daily"
}
diff --git a/src/views/AddExcursions.vue b/src/views/AddExcursions.vue
new file mode 100644
index 0000000..53fe853
--- /dev/null
+++ b/src/views/AddExcursions.vue
@@ -0,0 +1,180 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ For the past
+
+ days and with a
+
+ day
+ s
+ margin,
+ the MFE prices for the following
+ dates are missing:
+ , {{ useDateCalFormat(item) }}
+ all MFE prices have been updated.
+
+
+
+
+
+
+
+
+
+
+
To add MFE prices automatically, insert your API key in settings.
+
+
+
+
\ No newline at end of file
diff --git a/src/views/Daily.vue b/src/views/Daily.vue
index 06b64f6..3533c37 100644
--- a/src/views/Daily.vue
+++ b/src/views/Daily.vue
@@ -16,6 +16,8 @@ import { useGetExcursions, useGetTags, useGetAvailableTags, useUpdateAvailableTa
import { useCandlestickChart } from '../utils/charts';
+import { useGetMFEPrices } from '../utils/addTrades';
+
/* MODULES */
import Parse from 'parse/dist/parse.min.js'
import dayjs from 'dayjs'
@@ -67,10 +69,14 @@ let tradeSatisfaction
let tradeSatisfactionDateUnix
-let ohlcArray = []
+let ohlcArray = [] // array used for charts
+let ohlcv = [] // array used for MFE / excursion calculation (same as in addTrades.js)
const candlestickChartFailureMessage = ref(null)
+const apiIndex = ref(-1)
+const apiKey = ref(null)
+const apiSource = ref(null)
onBeforeMount(async () => {
@@ -174,6 +180,18 @@ async function clickTradesModal(param1, param2, param3) {
tradeIndexPrevious.value = Number(param2)
tradeIndex.value = Number(param3)
+ apiIndex.value = -1
+ let databentoIndex = apis.findIndex(obj => obj.provider === "databento")
+ let polygonIndex = apis.findIndex(obj => obj.provider === "polygon")
+
+ if (databentoIndex > -1 && apis[databentoIndex].key != "") {
+ apiIndex.value = databentoIndex
+ apiSource.value = "databento"
+ } else if (polygonIndex > -1 && apis[polygonIndex].key != "") {
+ apiIndex.value = polygonIndex
+ apiSource.value = "polygon"
+ }
+
let awaitClick = async () => {
modalDailyTradeOpen.value = true
@@ -209,22 +227,12 @@ async function clickTradesModal(param1, param2, param3) {
screenshot.type = null
/* GET OHLC / CANDLESTICK CHARTS */
- let index = -1
- let databentoIndex = apis.findIndex(obj => obj.provider === "databento")
- let polygonIndex = apis.findIndex(obj => obj.provider === "polygon")
- let apiSource
- if (databentoIndex > -1 && apis[databentoIndex].key != "") {
- index = databentoIndex
- apiSource = "databento"
- } else if (polygonIndex > -1 && apis[polygonIndex].key != "") {
- index = polygonIndex
- apiSource = "polygon"
- }
- if (index != -1) {
- let apiKey = apis[index].key
+
+ if (apiIndex.value != -1) {
+ apiKey.value = apis[apiIndex.value].key
let filteredTradesObject = filteredTrades[itemTradeIndex.value].trades[param3]
- if (apiKey) {
+ if (apiKey.value) {
if (filteredTradesObject.type == "future" && (databentoIndex === -1 || apis[databentoIndex].key === "")) {
candlestickChartFailureMessage.value = "You need a Databento API for Futures."
} else {
@@ -235,7 +243,7 @@ async function clickTradesModal(param1, param2, param3) {
let ohlcVolumes
if (ohlcArray.length == 0) {
console.log(" -> No symbol/date in ohlcArray")
- await getOHLC(filteredTradesObject.td, filteredTradesObject.symbol, filteredTradesObject.type, apiKey, apiSource)
+ await getOHLC(filteredTradesObject.td, filteredTradesObject.symbol, filteredTradesObject.type)
ohlcTimestamps = ohlcArray[0].ohlcTimestamps
ohlcPrices = ohlcArray[0].ohlcPrices
ohlcVolumes = ohlcArray[0].ohlcVolumes
@@ -250,7 +258,7 @@ async function clickTradesModal(param1, param2, param3) {
ohlcVolumes = ohlcArray[index].ohlcVolumes
} else {
console.log(" -> Symbol and/or date does not exist in ohlcArray")
- await getOHLC(filteredTradesObject.td, filteredTradesObject.symbol, filteredTradesObject.type, apiKey, apiSource)
+ await getOHLC(filteredTradesObject.td, filteredTradesObject.symbol, filteredTradesObject.type)
//console.log("ohlcArray "+JSON.stringify(ohlcArray))
let index = ohlcArray.findIndex(obj => obj.date === filteredTradesObject.td && obj.symbol === filteredTradesObject.symbol)
//console.log("index "+index)
@@ -262,7 +270,7 @@ async function clickTradesModal(param1, param2, param3) {
console.log(" -> there's an issues with OHLC")
}
}
- }
+ }
await useCandlestickChart(ohlcTimestamps, ohlcPrices, ohlcVolumes, filteredTradesObject, initCandleChart)
initCandleChart = false
@@ -522,6 +530,24 @@ async function updateExcursions() {
})
}
+const updateMFEPrices = async (param) => {
+ //console.log(" param " + JSON.stringify(param))
+ return new Promise(async (resolve, reject) => {
+ spinnerSetups.value = true
+ let symbols = []
+ ohlcv = []
+ for (let index = 0; index < param.length; index++) {
+ const element = param[index];
+ if(!symbols.includes(element.symbol)){
+ await getOHLC(element.td, element.symbol, element.type)
+ symbols.push(element.symbol)
+ }
+ await useGetMFEPrices()
+ }
+ //console.log(" ohlcv "+JSON.stringify(ohlcv))
+ resolve()
+ })
+}
/**************
* MISC
@@ -599,12 +625,16 @@ const filterDiary = (param) => {
return diaries.filter(obj => obj.dateUnix == param)
}
-function getOHLC(date, symbol, type, apiKey, apiSource) {
- if (apiSource === "databento") {
- console.log(" -> getting OHLC from "+apiSource+" for date "+date)
+
+
+function getOHLC(date, symbol, type) {
+ if (apiSource.value === "databento") {
+ console.log(" -> getting OHLC from " + apiSource.value + " for date " + useDateCalFormat(date))
+
return new Promise(async (resolve, reject) => {
let temp = {}
temp.symbol = symbol
+
let databentoSymbol = temp.symbol
let stype_in = "raw_symbol"
let toDate = dayjs(date * 1000).tz(timeZoneTrade.value).endOf('day').unix()
@@ -638,7 +668,7 @@ function getOHLC(date, symbol, type, apiKey, apiSource) {
'pretty_px': 'true',
'pretty_ts': 'true',
'map_symbols': 'true',
- 'username': apiKey
+ 'username': apiKey.value
}
axios.post('/api/databento', data)
@@ -646,6 +676,7 @@ function getOHLC(date, symbol, type, apiKey, apiSource) {
//console.log(" response "+JSON.stringify(response.data))
let res = await useCreateOHLCV(response.data, temp)
+ ohlcv.push(res) // used for MFE calculation (same as in addTrades.js)
let tempArray = {}
tempArray.date = date
@@ -679,7 +710,7 @@ function getOHLC(date, symbol, type, apiKey, apiSource) {
})
}
- else if (apiSource === "polygon") {
+ else if (apiSource.value === "polygon") {
let ticker
if (type === "put" || type === "call" || type === "option") {
@@ -696,7 +727,7 @@ function getOHLC(date, symbol, type, apiKey, apiSource) {
console.log(" --> Getting OHLC for ticker " + ticker + " on " + date)
return new Promise(async (resolve, reject) => {
- await axios.get("https://api.polygon.io/v2/aggs/ticker/" + ticker + "/range/1/minute/" + useDateCalFormat(date) + "/" + useDateCalFormat(date) + "?adjusted=true&sort=asc&limit=50000&apiKey=" + apiKey)
+ await axios.get("https://api.polygon.io/v2/aggs/ticker/" + ticker + "/range/1/minute/" + useDateCalFormat(date) + "/" + useDateCalFormat(date) + "?adjusted=true&sort=asc&limit=50000&apiKey=" + apiKey.value)
.then((response) => {
let tempArray = {}
@@ -706,6 +737,11 @@ function getOHLC(date, symbol, type, apiKey, apiSource) {
tempArray.ohlcPrices = []
tempArray.ohlcVolumes = []
+ let temp = {}
+ temp.symbol = symbol
+ temp.ohlcv = response.data.results
+ ohlcv.push(temp) // used for MFE calculation (same as in addTrades.js)
+
for (let index = 0; index < response.data.results.length; index++) {
const element = response.data.results[index];
@@ -772,6 +808,7 @@ function getOHLC(date, symbol, type, apiKey, apiSource) {
+
+
-# The project
-### About
There are numerous great and very powerful trading journals out there. However, I wanted to build a journal for traders who care about data security and privacy but also for individuals that need simplicity and flexibility.
By creating and sharing TradeNote as an open source project, I hope to help other days traders like myself store, discover and recollect trade patterns so they can become and remain consistent and profitable traders.
-
+
-### Project
-#### Contact
-For support or feedback, feel free to join our [Discord](https://discord.gg/ZbHekKYb85 "Discord"), or create an issue on this repository.
-Join the [emailing list](https://eleven.m-pages.com/tradenote "emailing list") to receive news about updates and new features.
-
-#### Upcoming features
-You can see upcoming features and project development on the [project page](https://github.com/orgs/Eleven-Trading/projects/1 "Project").
-
-#### Built with
-TradeNote is a responsive website that runs on VueJs, JS and HTML and uses [Parse](https://parseplatform.org/ "Parse") for its backend.
-
-#### Coffee
-If you like this project, please 🌟 this repository and don't hesitate to show me the money love ;)
-
-
-
-
-
-
-
-
-
-
-# TradeNote
-### Structure
-TradeNote is divided in 2 sections, each being an essential building block for becoming a consistent trading and managing your trading business.
-
-#### Analyze
-TradeNote offers a dashboard, daily view and calendar view so you can measure your progress. You can easily filter your trades by month or date range.
-
-
-#### Reflect
-With TradeNote you can keep a daily diary of your trading journey to work on your trader psychology as well as add annotated screenshots of interesting setups or your entries. You can also write your (yearly) playbook.
-
-### Trades, Assets and Brokers
-#### Supported trades
-TradeNote supports intraday and swing trades but keep in mind that the project arose from a personal need and as such is most widely used (and tested) for intraday stock trades (using TradeZero Broker).
-
-#### Supported Assets.
-Depending on the broker and thanks to the contribution of the community, you can import and journal the following assets in TradeNote: Stocks, Futures, Options (only tested on single options) and Forex.
-
-#### Supported Brokers
-Please look at the [brokers folder](https://github.com/Eleven-Trading/TradeNote/blob/main/brokers "brokers folder") to see the list of supported brokers and instructions for exporting and importing to TradeNote.
-
-You can contact me via [Discord](https://discord.gg/ZbHekKYb85 "Discord") if you wish to integrate your broker.
-
-Alternatively, you can use the [template](https://github.com/Eleven-Trading/TradeNote/blob/main/brokers/Template.csv "template") and adapt it to your export file.
-
-### Screenshots
-##### Dashboard
-
-
-##### Daily Page
-On daily page you can see your trades per day. You can add tags and a note to each of you trades. Moreover, you can specify your satisfaction with the trade (for example if you followed or not your rules) with thumbs up or down.
-
-
-
-
-##### Diary Page
-Besides notes for every trade, you can record things related to your every day trading, like feelings, moods and emotions or more technical related issues like patterns, in order to make important discoveries.
-
-
-
-
-
-##### Add Screenshots
-Upload screenshots of you trades ("Entry" option) or simply an interesting setup you have identified ("Setup" option) and make annotations for further analysis.
-
-
-
-
-
-### Full List of Features
-#### Import
-Import trades from your broker platform.
-
-#### Vizualize
-Visualize your trades on charts and get meaningful insight from your data.
-
-#### Filter
-Filter your results by date, position, patterns, mistakes and much more.
-
-#### Excursions
-Add MFE prices automatically for deeper efficiency analysis.
-
-#### Diary
-Keep a daily diary of your trading journey to work on your trader psychology.
-
-#### Satisfaction
-Mark each of your trades and days as good or bad depending on your trading rules.
-
-#### Screenshots
-Add and annotate chart screenshots to reflect on your trade entries or any interesting setups.
-
-#### Tag & Annotate
-Add pattern, mistake and note for each trade.
-
-#### Remove Imports
-Clean your imports with one simple click.
-
-#### Responsive
-Use TradeNote on your mobile devices.
-
-# Setup
-## Installation
-### ❗Important Notice ❗
-#### MongoDB
-TradeNote uses MongodDB as its database. Please make sure to follow MongoDB's recommendations and requirements before installing and running TradeNote with MongoDB.
-
-For detailed information, please read MongdoDB's [production notes ](https://www.mongodb.com/docs/manual/administration/production-notes/#platform-support "production notes").
-
-#### RaspberryPi
-Some users have experienced issues running MongoDB on a Raspberry Pi. At the time of writing, they managed to make it work using v4.4.8 of MongoDB rather than the latest version.
-
-#### Windows with Ubuntu VM
-MongoDB 5.0+ version is (currently) not compatible with Ubuntu VM on windows platform. Indeed, MongoDB 5.0+ needs a CPU that supports AVX, which seems like a known issue with latest version of MongoDB 5.0+ and Windows+Ubuntu VM. A workaround is to use MongoDB 4.4.18 (last known version that does not need AVX support) in the docker compose file.
-
-#### Other issues
-Here is the list of the other, most common issues
-- Your server / computer cannot run MongoDB (see above).
-- Docker is not running on your server / computer.
-- The required ports are not open on your server / computer.
-- You are running an incompatible NodeJs or MongoDB version with Parse Server ([compatibility table](https://github.com/parse-community/parse-server#compatibility "compatibility table")).
-- You already have a MongoDB instance running which is interfering with the installation process.
-
-If you have any other issues, please visite the [Discord](https://discord.gg/ZbHekKYb85 "Discord") #installation channel and use the search bar to see if your issue has already been discussed.
-
-
-### Docker Compose (recommended)
-#### Requirements
+# Installation
+For detailed installation and user guide, please visit the [documentation](https://tradenote.co/project-overview.html "documentation") page.
+## Docker Compose
+### Requirements
- Docker
- Docker Compose
-- Node 18.X
-#### Installation
+### Installation
1. Download the docker compose.yml file
2. Run `docker compose up -d`
@@ -156,13 +29,13 @@ You can then access the website on http://localhost:8080.
If you cannot access the website, please refer to the importante notice above (and try changing the mongo version) or get support via [Discord](https://discord.gg/ZbHekKYb85 "Discord")
-### Docker
-#### Requirements
+## Docker
+### Requirements
- Docker
- Node 18.X
- MongoDB
-#### Installation
+### Installation
You need to have a running MongoDB database. Please see their [Docker Hub](https://hub.docker.com/_/mongo "Docker Hub") for instructions.
Then, run the TradeNote image with its environment variables.
@@ -184,139 +57,19 @@ docker run \
- **MASTER_KEY**: Set a random string as master key, which will be used to make root connections to the backend (no spaces) (example: 12345)
- **TRADENOTE_PORT**: TradeNote port number, from which you wish to serve the website. (example: 8080)
-### Local installation (advanced)
-#### Docker
-If you want to run the latest version of TradeNote you can also build the image locally, directly from GitHub repository.
-
-1. Clone from github
-2. cd into TradeNote directory
-3. Run
- - For Docker Compose : Run `docker compose -f docker compose-local.yml up -d`
- - For Docker: run `docker build -f docker/Dockerfile . -t tradenote:`
-#### Dev Mode
-1. Clone the project (Master or Beta branch)
-2. cd into your project
-3. Run `npm install`
-4. Run `APP_ID=xxx MASTER_KEY=xxx ANALYTICS_OFF="true" MONGO_USER=xxx MONGO_PASSWORD=xxx TRADENOTE_DATABASE=xxx MONGO_URL=xxx MONGO_PORT=xxx TRADENOTE_PORT=xxx NODE_ENV='dev' node index.mjs`
-
-## First Steps
-### Registering a User
+# Quick Start
+## Registering a User
Start by registering a user. Visit `http://localhost:8080/register` to register a TradeNote user.
- Use any (random) email and set a password.
- Choose your broker and/or account timezone.
-### Importing Trades
-#### Instructions
+## Importing Trades
Please make sure to follow the instructions in the [brokers folder](https://github.com/Eleven-Trading/TradeNote/blob/main/brokers "brokers folder") for exporting and importing trades.
-Community members have also created custom scripts to convert export files to fit the template in [custom scripts](https://github.com/Eleven-Trading/TradeNote/blob/main/brokers/conversionScripts.md "custom scripts").
-
-
-#### Swing trades
-❗Important Notice❗
-- Importing swing trades is currently in beta phase and being tested. Please use [Discord](https://discord.gg/ZbHekKYb85 "Discord") if you have questions or comments.
-- Importing swing trades is prone to P&L Discrepancy and it is therefore crucial to follow the instructions below.
-
-##### Avoiding P&L Discrepancy
-To avoid open positions and/or P&L Discrepancy when importing swing trades, please make sure of the following.
-1. Begin importing from a point in time where you were flat (no previous open positions). If you begin importing from a point in time where you had pre-existing positions, there is a chance the data will be incorrect as TradeNote doesn’t know about these existing positions.
-2. Alternatively, before importing your file, if you know it contains execution(s) from an open position (and that you're closing or adding to), simply remove that/these execution(s).
-
-Example 1 (recommended)
-- You remember that on the 10th of August 2022 you did not have any open trades from before the 10th of August 2022
-- You import your trades between 10/08/22-30/11/2023.
-- From there, if you later decide to import trades before 10/08/22 they will likely be inaccurate and cause future discrepancies.
-- If you import trades in between the dates, for example on the 12/03/2023, it will say "already imported" (this is standard TradeNote behavior - you cannot import already imported dates)
-- If you import trades after 30/11/23 it will work.
-- The best is to start with a clean TradeNote database but it is not mandatory as long as you respect these steps.
-
-Example 2 (advanced)
-- You want to start your imports 10th of August 2022, but you know you have 1 open trade from before.
-- You want to import your trades between 10/08/22-30/11/2023 and you know that you closed the open trade on the 20th of August 2022.
-- Edit your export file on the 20th of August 2022 by removing all executions related to closing the mentioned trade.
-- The reste of the process is the same as example 1.
-
-##### P&L Calculation and View
-When you have an open trade you will see "Open" in the time column on the daily page and "Closed" when the swing trade has been closed. Hover your mouse over the question mark to get information when the trade was opened and closed.
-
-P&L as well as partial close is calculated on the closing day.
-
-## Updating
-The following information is just for your convienance. We encourage you to make your own research on how to update running applications.
-
-### Using Docker Compose
-1. Pull the latest Docker image: You can do this by running docker compose pull < service-name > in your terminal. This command will pull the latest image for the specified service from the Docker registry.
-2. Stop the running service: You can stop the running service with the command docker compose stop < service-name >. This will stop the service without removing it.
-3. Remove the existing container: You can remove the existing container with the command docker compose rm < service-name >. This will remove the stopped container.
-4. Start the service with the updated image: You can start the service with the updated image with the command docker compose up -d < service-name >. The -d flag runs the container in detached mode, which means it will run in the background.
-5. Verify the update: You can verify that the update was successful by checking the logs of the container with the command docker compose logs < service-name > or by checking the running service's behavior.
-
-Remember to replace < service-name> with the name of your service as defined in your docker compose.yml file.
-
-This is a general process and may vary depending on the specifics of your application and your Docker Compose setup. Always make sure to test your changes in a safe environment before deploying them to production.
-
-### Using Docker
-1. Pull the latest Docker image: You can do this by running docker pull < image-name >:< tag > in your terminal. This command will pull the latest version of the specified image from Docker Hub.
-2. Stop the running container: You can stop the running container with the command docker stop < container-id >. Replace < container-id > with the ID of the running container.
-3. Remove the existing container: You can remove the existing container with the command docker rm < container-id >. Replace < container-id > with the ID of the stopped container.
-4. Start a new container with the updated image: You can start a new container with the updated image with the command docker run < image-name >:< tag >. Replace < image-name > and < tag > with the name and tag of the updated image.
-5. Verify the update: You can verify that the update was successful by checking the logs of the container with the command docker logs < container-id > or by checking the running container's behavior.
-
-Remember to replace < image-name >, < tag >, and < container-id > with the appropriate values for your application and Docker setup.
-
-This is a general process and may vary depending on the specifics of your application and your Docker setup. Always make sure to test your changes in a safe environment before deploying them to production.
-
-
-
-## TradeNote API (Experimental)
-TradeNote provides an API so that you can setup an auto import of your trades. Please note that this a very experimental feature AND unfortunately, I will not be able to provide support on how to make a POST request.
-
-**POST /api/trades**
-
-Base url: < your tradenote url >
-
-The trades endpoint allows you to import trade executions directly to the database.
-
-### Headers
-- "api-key" (string): your TradeNote API key from your settings page.
-
-### Request Body
-- "data" (array): contains all the executions, with same attributes and value format as the export file.
-- "selectedBroker" (string): your broker name (example: "tradeZero").
-- "uploadMfePrices" (bool): true or false if you want to upload MFE prices automatically. Requires you have added the Polygon API key.
-
-## Side note
-### Parse
-This project uses [Parse](https://github.com/parse-community "Parse") as its backend framework, for the following reasons:
-1. Manage the authentication (flow)
-2. Parse is a great framework for all API communications with the mongo database
-3. Parse acts as the server so that TradeNote does not need to run any server on its own, making it faster and lighter.
-
-### Viewing the database (optional)
-Additionally, if you want to view and manage the database, you can use [MongoDB Compass](https://github.com/parse-community "MongoDB Compass") or install and run the [Parse Dashboard](https://github.com/parse-community/parse-dashboard "Parse Dashboard").
-
-### PostHog
-This projects uses [PostHog](https://github.com/PostHog/posthog "PostHog") as its product analytics suite to collect anonymous analytics about TradeNote installations and page views. This helps me better understand if and how people are using TradeNote and evaluate the outreach of my project. If you want to opt-out of this program, you can simply add `-e ANALYTICS_OFF=true` when running the docker image.
-
-
-## Backup data
-### Persistent data
-During installation, mongoDB runs with persistent data. This way, if you restart or update your mongoDB container, your data will not be lost.
-
-### Backup mongoDB
-Additionally, you can, and should, backup your database.
-
-For convenience, here is an example using [s3cmd](https://s3tools.org/s3cmd "s3cmd") for backing up your database. As this is not part of the TradeNote projet, I will unfortunately not be able to provide support on this part. But you will find more information about this on google and stackoverflow.
-1. Install [s3cmd](https://s3tools.org/s3cmd "s3cmd")
-2. Configure the s3cfg config file
-3. Run the [bash file](https://github.com/Eleven-Trading/TradeNote/blob/main/backup-mongodb.sh "bash file") with the correct variables
-
-
# Contribute
I'm a trader and recreational developer. My days are very packed but I will do my best to answer your questions and update the code when needed. As such, do not hesitate to contact me if you would like to contribute and help improve this project. Things to work on and improve:
- Add support to other trading platforms
-- Currently, the code has only been tested for day trading and it would be interesting to add support for swing and multi-day trading
- Improve front end layout and develop new ideas
- And more...
From 4f4e3dc5f4b1524ebba4abc5b4b861ce149de080 Mon Sep 17 00:00:00 2001
From: El Even <>
Date: Mon, 11 Nov 2024 22:53:03 +0100
Subject: [PATCH 129/169] 18.18.2
---
package-lock.json | 4 ++--
package.json | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index fe4a0de..b9e5fa6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "tradenote",
- "version": "18.18.1",
+ "version": "18.18.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "tradenote",
- "version": "18.18.1",
+ "version": "18.18.2",
"license": "GPL-3.0-or-later",
"dependencies": {
"axios": "^1.7.5",
diff --git a/package.json b/package.json
index 64672d5..d5957f0 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "tradenote",
- "version": "18.18.1",
+ "version": "18.18.2",
"description": "TradeNote helps traders store, discover and recollect trade patterns so they can become and remain consistent and profitable traders",
"author": "eleven.trading",
"license": "GPL-3.0-or-later",
From 40196a994ed0b77d53f76087c904290be0bd4ec7 Mon Sep 17 00:00:00 2001
From: El Even <>
Date: Mon, 11 Nov 2024 22:57:45 +0100
Subject: [PATCH 130/169] no message
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index b8cbcaf..827e400 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/src/views/CheckoutSuccess.vue b/src/views/CheckoutSuccess.vue
new file mode 100644
index 0000000..679c197
--- /dev/null
+++ b/src/views/CheckoutSuccess.vue
@@ -0,0 +1,86 @@
+
+
+
+
+
+ We appreciate your business! A confirmation email will be sent to .
+
+ If you have any questions, please email hello@eleven.trading.
+
+
+
+
\ No newline at end of file
From fc94720bd40a1dc01c388bd38f634211e6756e68 Mon Sep 17 00:00:00 2001
From: Christophe Dervieux
Date: Mon, 9 Dec 2024 10:41:55 +0100
Subject: [PATCH 138/169] Correct field for selection in IBKR broker
FlexQueries
---
brokers/README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/brokers/README.md b/brokers/README.md
index fe295d3..e988e29 100644
--- a/brokers/README.md
+++ b/brokers/README.md
@@ -51,10 +51,10 @@ Note : make sure to input one execution per line, not aggregated trade. So one l
2. Sections
1. Click Trade Confirmation and only select Executions
2. From there, you can either
- 1. Check the following boxes manually: Account ID, Date/Time, Settle Date, Currency, Underlying Symbol, Symbol, Buy/Sell, Quantity, Price, Commission, Code, Broker Execution Commission, Broker Clearing Commission, Third-Party Execution Commission, Thirs-Party Clearing Commission, Other Commissions, Proceeds, Net Cash, Trade ID, Order ID, AssetClass, Multiplier, Put/Call
+ 1. Check the following boxes manually: Account ID, Date/Time, Settle Date, Currency, Underlying Symbol, Symbol, Buy/Sell, Quantity, Price, Commission, Code, Broker Execution Commission, Broker Clearing Commission, Third-Party Execution Commission, Third-Party Clearing Commission, Other Commission, Proceeds, Net Cash, Trade ID, Order ID, Asset Class, Multiplier, Put/Call
2. Or execute the following script using your browser's DevTools console:
```js
- const fields=["Account ID","Date/Time","Settle Date","Currency","Underlying Symbol","Symbol","Buy/Sell","Quantity","Price","Commission", "Code", "Broker Execution Commission","Broker Clearing Commission","Third-Party Execution Commission","Thirs-Party Clearing Commission","Other Commissions","Proceeds","Net Cash","Trade ID","Order ID","AssetClass","Multiplier","Put/Call"],container=document.querySelector('tbody[class="ui-sortable"]'),trs=container.querySelectorAll("tr");trs.forEach(e=>{let r=e.querySelectorAll("td"),t=r[1].childNodes[0].textContent;if(fields.includes(t)){let i=e.querySelector('input[type="checkbox"]');i.click()}});
+ const fields=["Account ID","Date/Time","Settle Date","Currency","Underlying Symbol","Symbol","Buy/Sell","Quantity","Price","Commission", "Code", "Broker Execution Commission","Broker Clearing Commission","Third-Party Execution Commission","Third-Party Clearing Commission","Other Commission","Proceeds","Net Cash","Trade ID","Order ID","Asset Class","Multiplier","Put/Call"],container=document.querySelector('tbody[class="ui-sortable"]'),trs=container.querySelectorAll("tr");trs.forEach(e=>{let r=e.querySelectorAll("td"),t=r[1].childNodes[0].textContent;if(fields.includes(t)){let i=e.querySelector('input[type="checkbox"]');i.click()}});
```
4. Scroll to the bottom of the dialog and select Save
3. Delivery Configuration
From cc1789feda4ddcab4dedbf7d5c480e9298d46fc0 Mon Sep 17 00:00:00 2001
From: El Even <>
Date: Tue, 10 Dec 2024 17:41:14 +0100
Subject: [PATCH 139/169] working cloud version.
---
index.mjs | 106 +++++++++++++++++++++----------
src/components/LoginRegister.vue | 6 +-
src/components/Nav.vue | 26 ++++++--
src/router/index.js | 61 ++----------------
src/utils/utils.js | 34 +++++++++-
src/views/AddTrades.vue | 10 ++-
src/views/Checkout.vue | 20 +++++-
src/views/CheckoutSuccess.vue | 70 +++++++++++++-------
8 files changed, 207 insertions(+), 126 deletions(-)
diff --git a/index.mjs b/index.mjs
index 5f5c127..2d5b4ee 100644
--- a/index.mjs
+++ b/index.mjs
@@ -9,10 +9,14 @@ import * as Vite from 'vite'
import { MongoClient } from "mongodb"
import Proxy from 'http-proxy'
import { useImportTrades, useGetExistingTradesArray, useUploadTrades } from './src/utils/addTrades.js';
-import { currentUser, uploadMfePrices, existingTradesArray, tradesData, existingImports } from './src/stores/globals.js';
+import { currentUser, uploadMfePrices} from './src/stores/globals.js';
import { useGetTimeZone } from './src/utils/utils.js';
import Stripe from 'stripe';
-const stripe = new Stripe(process.env.STRIPE);
+let stripe
+if(process.env.STRIPE){
+ stripe = new Stripe(process.env.STRIPE);
+}
+
let stripePriceId
@@ -39,6 +43,7 @@ console.log(' -> Database URI ' + hiddenDatabaseURI)
let tradenoteDatabase = process.env.TRADENOTE_DATABASE
var app = express();
+app.use(express.json());
const port = process.env.TRADENOTE_PORT;
const PROXY_PORT = 39482;
@@ -63,21 +68,21 @@ const startIndex = async () => {
const runServer = async () => {
console.log("\nRUNNING SERVER");
-
+
return new Promise(async (resolve, reject) => {
if (process.env.NODE_ENV == 'dev') {
// Set up proxy for development environment
const proxy = new Proxy.createProxyServer({
target: { host: 'localhost', port: PROXY_PORT },
});
-
+
// Middleware to handle API routes
app.use('/api/*', (req, res, next) => {
// Handle API routes here (e.g., route to a different backend or do something custom)
//console.log("Handling API route:", req.url);
next(); // Continue processing the request for /api/* routes
});
-
+
// Proxy all other routes to Vite
app.use((req, res, next) => {
if (req.url.startsWith('/api/')) {
@@ -85,7 +90,7 @@ const startIndex = async () => {
}
proxy.web(req, res); // Proxy all other routes to Vite
});
-
+
// Start Vite dev server
const vite = await Vite.createServer({ server: { port: PROXY_PORT } });
vite.listen();
@@ -99,21 +104,21 @@ const startIndex = async () => {
// You can add logic to process the API requests, for example:
// if (req.url === '/api/session-status') { ... }
});
-
+
// Serve the static files from the 'dist' folder
app.use(express.static('dist'));
-
+
// Catch-all route for any other requests (to handle SPAs)
app.get('*', (request, response) => {
response.sendFile(path.resolve('dist', 'index.html'));
});
-
+
console.log(" -> Running prod server");
resolve();
}
});
};
-
+
const setupParseServer = async () => {
console.log("\nSTARTING PARSE SERVER")
@@ -188,32 +193,67 @@ const startIndex = async () => {
* CLOUD
**********************************************/
- app.post("/api/checkCloud", (req, res) => {
- //console.log("\nAPI : posthog")
- if (process.env.CLOUD) {
- res.status(200).send('OK');
- } else {
- res.status(403).send('Forbidden');
- }
- });
+ app.post("/api/checkCloudPayment", async(req, res) => {
+ // Used for checking if can access add*, in case it's a paying user
+ let currentUser = req.body.currentUser
+ //console.log(" currentUser "+JSON.stringify(currentUser))
+ const trialPeriod = 2
+ //console.log(" current user " + JSON.stringify(req.body.currentUser))
+ if (process.env.STRIPE) {
+ console.log("\nAPI : checkCloudPayment")
+ // Check if user is stripe customer
+
+ // Check if user has paying customer
+ if (currentUser.hasOwnProperty("paymentService") && currentUser.paymentService.hasOwnProperty("subscriptionId")) {
+ /// if yes, let inn, status 200
+ const activeSubscription = ['active', 'trialing', 'past_due']
+ const subscription = await stripe.subscriptions.retrieve(currentUser.paymentService.subscriptionId)
+ if(activeSubscription.includes(subscription.status)){
+ console.log(" -> User has valid subscription.");
+ res.status(200).send('OK');
+ }else{
+ console.log(" -> User has invalid subscription.");
+ res.status(403).send('Forbidden');
+ }
+
+ }
- app.post("/api/checkCloudPayment", (req, res) => {
- //console.log("\nAPI : posthog")
- if (process.env.CLOUD) {
- res.status(200).send('OK');
- // Check if user has paid
- /// if yes, let inn, status 200
- /// If not, check if user is older than 7 days
- //// if older, redirect to stripe / status 202
- //// else, let inn, status 200
+ /// If not, check if user is within trial period
+ else {
+
+ // Convert createdAt to a Date object
+ const createdAtDate = new Date(currentUser.createdAt);
+
+ // Get the current time
+ const currentDate = new Date();
+
+ // Calculate the time difference in milliseconds
+ const timeDifference = currentDate - createdAtDate;
+
+ // Convert the time difference to days
+ const differenceInDays = timeDifference / (1000 * 60 * 60 * 24); // Milliseconds to days
+
+ //// if older, redirect to stripe / status 403
+ if (differenceInDays > trialPeriod) {
+ console.log(" -> User is past trial period.");
+ res.status(403).send('Forbidden');
+ }
+
+ //// else, let inn, status 200
+ else {
+ console.log(" -> User is within trial period.");
+ res.status(200).send('OK');
+ }
+ }
+
+
+
+
} else {
res.status(200).send('OK');
}
});
-
- app.use(express.json());
-
app.post('/api/create-checkout-session', async (req, res) => {
const session = await stripe.checkout.sessions.create({
ui_mode: 'embedded',
@@ -236,9 +276,9 @@ const startIndex = async () => {
try {
console.log("Getting session status");
const session = await stripe.checkout.sessions.retrieve(req.query.session_id);
-
+
//console.log("Session retrieved:", JSON.stringify(session));
-
+
res.send({
session: session,
status: session.status,
@@ -255,7 +295,7 @@ const startIndex = async () => {
app.post("/api/updateSchemas", async (req, res) => {
- if (!process.env.CLOUD) {
+ if (!process.env.STRIPE) {
//console.log("\nAPI : post update schema")
let rawdata = fs.readFileSync('requiredClasses.json');
diff --git a/src/components/LoginRegister.vue b/src/components/LoginRegister.vue
index 9a4d8c6..5144872 100644
--- a/src/components/LoginRegister.vue
+++ b/src/components/LoginRegister.vue
@@ -1,7 +1,7 @@
@@ -175,19 +189,19 @@ function getLatestVersion() {
Add