import "./tracing.js" import express from "express"; import cors from "cors"; import fetch from "node-fetch"; const app = express(); import pg from "pg"; const client = new pg.Client( "postgresql://crate@crate1.home.neb:5432/oversite" ); client.connect(); //var ASKGAP = 0.9955; //var BIDGAP = 1.0045; var ASKGAP = 0.0008; var BIDGAP = 0.0008; var NTP = 0.0004; const port = 3000; const host = "0.0.0.0"; const STATE = {}; const PRICES = {}; import axios from "axios"; const APIURL = "https://api-fxpractice.oanda.com/v3"; const STREAMURL = "https://stream-fxpractice.oanda.com/v3"; const APIURL_DEMO = "https://api-fxpractice.oanda.com/v3"; const STREAMURL_DEMO = "https://stream-fxpractice.oanda.com/v3"; const accounts = { "1": { ACCT: "101-001-8005237-001", APIKEY: "e88218d201bd344c2dc3c469f8f8d1f3-e77504680a17f51f0baecf9dababa40b", }, "4": { ACCT: "101-001-8005237-002", APIKEY: "e88218d201bd344c2dc3c469f8f8d1f3-e77504680a17f51f0baecf9dababa40b", }, "2": { APIKEY: "b954456a3f4ac735de2555e1af50abf7-ed83ace2f9fb86412b76608daefc73a5", ACCT: "101-001-23367262-002", }, "3": { APIKEY: "d4ea6095fe8017841279416437520aee-fa23a0556fb501520ceedbff5f405267", ACCT: "101-002-26241098-001", }, }; function processTP() { try { for (const pair in PRICES) { for (const acct in STATE) { if (typeof(STATE[acct][pair]) !== 'undefined' && STATE[acct][pair]["watch"] == "ask") { STATE[acct][pair]["currentPrice"] = PRICES[pair]["ask"]; STATE[acct][pair]["delta"] = PRICES[pair]["ask"] - STATE[acct][pair]["base"]; if (pair.includes("JPY")) { STATE[acct][pair]["delta"] = STATE[acct][pair]["delta"] * 100; } else { STATE[acct][pair]["delta"] = STATE[acct][pair]["delta"] * 10000; } if (STATE[acct][pair]["TP"] == true) { STATE[acct][pair]["TPVal"] = STATE[acct][pair]["NTPVal"]; } let newtrigger = PRICES[pair]["ask"] - STATE[acct][pair]["TPVal"] if (STATE[acct][pair]["trigger"] < newtrigger) { STATE[acct][pair]["trigger"] = newtrigger; } if ( PRICES[pair]["ask"] < STATE[acct][pair]["trigger"] ) { closeOrder( accounts[acct]["ACCT"], accounts[acct]["APIKEY"], STATE[acct][pair]["trade_id"] ); delete STATE[acct][pair]; } try { if (STATE[acct][pair]["base"] + STATE[acct][pair]["TPVal"] <= PRICES[pair]["ask"]) { STATE[acct][pair]["TP"] = true; } } catch(error) { console.log(error); } } else if (typeof(STATE[acct][pair]) !== 'undefined' && STATE[acct][pair]["watch"] == "bid") { STATE[acct][pair]["currentPrice"] = PRICES[pair]["bid"]; STATE[acct][pair]["delta"] = STATE[acct][pair]["base"] - PRICES[pair]["bid"]; if (pair.includes("JPY")) { STATE[acct][pair]["delta"] = STATE[acct][pair]["delta"] * 100; } else { STATE[acct][pair]["delta"] = STATE[acct][pair]["delta"] * 10000; } if (STATE[acct][pair]["TP"] == true) { STATE[acct][pair]["TPVal"] = STATE[acct][pair]["NTPVal"]; } let newtrigger = PRICES[pair]["bid"] + STATE[acct][pair]["TPVal"] if (STATE[acct][pair]["trigger"] > newtrigger) { STATE[acct][pair]["trigger"] = newtrigger; } if ( PRICES[pair]["bid"] > STATE[acct][pair]["trigger"] ) { closeOrder( accounts[acct]["ACCT"], accounts[acct]["APIKEY"], STATE[acct][pair]["trade_id"] ); delete STATE[acct][pair]; } try { if (STATE[acct][pair]["base"] - STATE[acct][pair]["TPVal"] >= PRICES[pair]["bid"]) { STATE[acct][pair]["TP"] = true; } } catch(error) { console.log(error); } } } } } catch (e) { console.log("error prices"); console.log(e); } } function saveState() { client.query(`insert into osapi_state (account_state, state_date) values ($1, now())`, [JSON.stringify(STATE) ]); } setInterval(processTP, 1500); setInterval(saveState, 3000); async function loadState() { let res = await client.query(`select account_state from osapi_state order by state_date desc limit 1`); if (res.rows.length !== 0) { var st ; console.log(res['rows'][0]['account_state']); if (typeof(res['rows'][0]['account_state']) === 'object') { st = res['rows'][0]['account_state']; } else { st = JSON.parse(res['rows'][0]['account_state']); } for (const k in st) { STATE[k] = st[k]; } console.log(STATE); } } async function getTransactionsAll(account, acct_id, api_key) { console.log("getTransactionsAll"); for (const rg of [...Array(32).keys()]) { try { let f = rg * 1000 + 1; let to = f + 1000; const response = await axios.request({ url: `${APIURL}/accounts/${acct_id}/transactions/idrange?from=${f}&to=${to}`, method: "get", headers: { Authorization: `Bearer ${api_key}`, }, }); for (const t of response.data.transactions) { let qty = 0; let tp = ""; if (typeof t["instrument"] !== undefined) { tp = t["instrument"]; } if (typeof t["units"] !== undefined) { qty = t["units"]; } await client.query( `insert into orders (order_id, account_id, tpair, order_type, order_ref, order_reason, order_date, order_data, quantity) values ($1,$2,$3,$4,$5,$6,$7,$8,$9)`, [t.id, account, tp, t.type, t.batchID, t.reason, t.time, t, qty] ); } } catch (error) { console.log(error); } } } async function getTransactions(account, acct_id, api_key) { console.log("getTransactions"); try { const res = await client.query( `SELECT max(order_id) as order_id from orders where account_id = ${account}` ); let f = res.rows[0].order_id; let to = f + 1000; const response = await axios.request({ url: `${APIURL}/accounts/${acct_id}/transactions/idrange?from=${f}&to=${to}`, method: "get", headers: { Authorization: `Bearer ${api_key}`, }, }); for (const t of response.data.transactions) { let qty = 0; let tp = ""; if (typeof t["instrument"] !== undefined) { tp = t["instrument"]; } if (typeof t["units"] !== undefined) { qty = t["units"]; } await client.query( `insert into orders (order_id, account_id, tpair, order_type, order_ref, order_reason, order_date, order_data, quantity) values ($1,$2,$3,$4,$5,$6,$7,$8,$9)`, [t.id, account, tp, t.type, t.batchID, t.reason, t.time, t, qty] ); } } catch (error) { console.error(error); } } async function getTrades(acct_id, api_key) { console.log("getTrades"); try { const response = await axios.request({ url: `${APIURL}/accounts/${acct_id}/openTrades`, method: "get", headers: { Authorization: `Bearer ${api_key}`, }, }); return response; } catch (error) { console.error(error); } } async function getTradesByInstrument(acct_id, api_key, instrument) { console.log("getTradesByInstrument"); try { const response = await axios.request({ url: `${APIURL}/accounts/${acct_id}/trades?instrument=${instrument}`, method: "get", headers: { Authorization: `Bearer ${api_key}`, }, }); //console.log(response) if (response.data.trades.length == 0) { return; } else { return response.data.trades[0]; } } catch (error) { console.error(error); } } async function getPositions(acct_id, api_key) { try { const response = await axios.request({ url: `${APIURL}/accounts/${acct_id}/openPositions`, method: "get", headers: { Authorization: `Bearer ${api_key}`, }, }); return response; } catch (error) { console.error(error); } } async function order(acct_id, api_key, instrument, quantity) { console.log("order"); try { let dist = NTP; let pdist = NTP; if (instrument.includes("JPY")) { dist = NTP * 100; pdist = NTP * 100; } let data = { order: { /*"trailingStopLossOnFill": { "timeInForce": "GTC", "distance": dist },*/ /*"takeProfitOnFill": { "distance": pdist },*/ timeInForce: "FOK", instrument: instrument, units: quantity, type: "MARKET", positionFill: "DEFAULT", }, }; const response = await axios.request({ url: `${APIURL}/accounts/${acct_id}/orders`, method: "post", headers: { Authorization: `Bearer ${api_key}`, }, data: data, }); return response; } catch (error) { console.error(error); } } async function closeOrder(acct_id, api_key, tradeID) { try { const response = await axios.request({ url: `${APIURL}/accounts/${acct_id}/trades/${tradeID}/close`, method: "put", headers: { Authorization: `Bearer ${api_key}`, }, }); console.log(response.data); return response; } catch (error) { console.error(error); } } async function trailingStopLoss(acct_id, api_key, instrument) { const trade = await getTradesByInstrument(acct_id, api_key, instrument); const tradeID = trade.id; console.log(tradeID); if (!tradeID) { return; } dist = "0.00164"; if (instrument.includes("JPY")) { dist = "0.16"; } try { let data = { trailingStopLoss: { timeInForce: "GTC", distance: dist, }, }; const response = await axios.request({ url: `${APIURL}/accounts/${acct_id}/trades/${tradeID}/orders`, method: "put", headers: { Authorization: `Bearer ${api_key}`, }, data: data, }); console.log(data); console.log(response.data); return response; } catch (error) { console.error(error); } } async function stopLoss(acct_id, api_key, tradeID, price) { try { let data = { stopLoss: { timeInForce: "GTC", distance: "0.30", //"price": price }, }; const response = await axios.request({ url: `${APIURL}/accounts/${acct_id}/trades/${tradeID}/orders`, method: "put", headers: { Authorization: `Bearer ${api_key}`, }, data: data, }); console.log(data); console.log(response.data); return response; } catch (error) { console.error(error); } } async function takeProfit(acct_id, api_key, tradeID, dist) { try { let data = { takeProfit: { timeInForce: "GTC", distance: dist, }, }; const response = await axios.request({ url: `${APIURL}/accounts/${acct_id}/trades/${tradeID}/orders`, method: "put", headers: { Authorization: `Bearer ${api_key}`, }, data: data, }); return response; } catch (error) { console.error(error); } } app.get("/closeAll", async (req, res) => { res.json("disabled for now"); }); app.get("/asdfadsfasdfcloseAll", async (req, res) => { for (const acct in STATE) { for (const pair in STATE[acct]) { closeOrder( accounts[acct]["ACCT"], accounts[acct]["APIKEY"], STATE[acct][pair]["trade_id"] ); delete STATE[acct][pair]; } } for (const account of Object.keys(accounts)) { let trades = await getTrades( accounts[account]["ACCT"], accounts[account]["APIKEY"] ); for (const t of trades.data["trades"]) { await closeOrder( accounts[account]["ACCT"], accounts[account]["APIKEY"], t["id"] ); } } saveState(); res.json("done"); }); app.get("/tradesData", async (req, res) => { let r = []; for (const account of Object.keys(accounts)) { let response = await getTrades( accounts[account]["ACCT"], accounts[account]["APIKEY"] ); try { //Object.(response.data['trades']).forEach(([a, t]) =>{ response.data["trades"].forEach((t) => { t["Account"] = account; delete t["lastTransactionID"]; delete t["trailingStopLossOrder"]; console.log(t); r.push(t); }); } catch (error) { console.log(error); } } res.header("Access-Control-Allow-Origin", "*"); res.json(r); }); app.get("/transactionsAll/:account", async (req, res) => { let account = req.params.account; let r = {}; let response = await getTransactionsAll( account, accounts[account]["ACCT"], accounts[account]["APIKEY"] ); try { r = {}; } catch (error) { console.log(error); } res.header("Access-Control-Allow-Origin", "*"); res.json("ok"); }); app.get("/transactions", async (req, res) => { let r = {}; for (const account of Object.keys(accounts)) { let response = await getTransactions( account, accounts[account]["ACCT"], accounts[account]["APIKEY"] ); try { r = {}; } catch (error) { console.log(error); } } res.header("Access-Control-Allow-Origin", "*"); res.json("ok"); }); app.get("/trades", async (req, res) => { let r = {}; for (const account of Object.keys(accounts)) { let response = await getTrades( accounts[account]["ACCT"], accounts[account]["APIKEY"] ); try { r[account] = response.data; STATE[account] = {}; for (const t in response.data["trades"]) { let td = response["data"]["trades"][t]; console.log(td); //let delta = 0.0012; let delta = 0; let askGap = ASKGAP; let bidGap = BIDGAP; let ntp = NTP; if (td["instrument"].includes("JPY")) { //delta = 0.12; delta = 0; askGap = ASKGAP * 100; bidGap = BIDGAP * 100; ntp = NTP * 100; } if (typeof(STATE[account][td["instrument"]]) === 'undefined') { STATE[account][td["instrument"]] = {}; } STATE[account][td["instrument"]]["TP"] = false; STATE[account][td["instrument"]]["qty"] = td["quantity"]; STATE[account][td["instrument"]]["trade_id"] = td["id"]; if (td["currentUnits"] > 0) { STATE[account][td["instrument"]]["base"] = Number(td["price"]) + delta; STATE[account][td["instrument"]]["watch"] = "ask"; STATE[account][td["instrument"]]["trigger"] = (Number(td["price"]) + delta) - askGap; STATE[account][td["instrument"]]["TPVal"] = askGap; STATE[account][td["instrument"]]["NTPVal"] = ntp; } else { STATE[account][td["instrument"]]["base"] = Number(td["price"]) - delta; STATE[account][td["instrument"]]["watch"] = "bid"; STATE[account][td["instrument"]]["trigger"] = (Number(td["price"]) - delta) + bidGap; STATE[account][td["instrument"]]["TPVal"] = bidGap; STATE[account][td["instrument"]]["NTPVal"] = ntp; } } } catch (error) { console.log(error); } } res.header("Access-Control-Allow-Origin", "*"); res.json(r); }); app.get("/accounts/:user_id", async (req, res) => { let accts = await client.query( `select * from accounts where user_id = ${req.params.user_id} ` ); res.header("Access-Control-Allow-Origin", "*"); res.json(accts.rows); }); app.get("/accounts", async (req, res) => { let accts = await client.query(`select * from accounts `); res.header("Access-Control-Allow-Origin", "*"); let r = {}; console.log(accts); for (const a of accts.rows) { if (typeof r[a["user_id"]] === "undefined") { r[a["user_id"]] = [a]; } else { r[a["user_id"]].push(a); } } res.json(r); }); app.get("/accounts/del/:user_id/:account_id", async (req, res) => { await client.query( `delete from accounts where user_id = ${req.params.user_id} and id = ${req.params.account_id}` ); res.header("Access-Control-Allow-Origin", "*"); res.json("ok"); }); app.get("/accounts/add/:user_id/:a_number/:akey", async (req, res) => { await client.query( `insert into accounts (user_id, account_number, apikey, account_type) values (${req.params.user_id},'${req.params.a_number}','${req.params.akey}', 'oanda')` ); res.header("Access-Control-Allow-Origin", "*"); res.json("ok"); }); app.get("/trailingStop/:instrument", async (req, res) => { const response = await trailingStopLoss(ACCT, APIKEY, req.params.instrument); console.log(response); res.header("Access-Control-Allow-Origin", "*"); res.json(response.data); }); app.get("/tradesByInstrument/:instrument", async (req, res) => { res.header("Access-Control-Allow-Origin", "*"); r = ""; data = await client.query( `select * from orders where account_id = 2 and order_date > now()- interval '1 day'` ); for (const row of data.rows) { delete row["order_data"]; r += `${Object.values(row).join(",")} \n`; } //r[account] = await getTradesByInstrument(accounts[2]['ACCT'], accounts[2]['APIKEY'], req.params.instrument); res.header("Content-Type", "text/csv"); res.send(r); }); app.get("/prices", async (req, res) => { res.header("Access-Control-Allow-Origin", "*"); res.json(PRICES); }); app.get("/state", async (req, res) => { res.header("Access-Control-Allow-Origin", "*"); res.json(STATE); }); app.get("/order/:instrument/:quantity", async (req, res) => { client.query(`insert into order_log(order_date,order_body) values (now(), $1)`, [`${req.params.instrument} ${req.params.quantity}`]); let r = {}; for (const account of Object.keys(accounts)) { let td = await getTradesByInstrument( accounts[account]["ACCT"], accounts[account]["APIKEY"], req.params.instrument ); if (td == null || (td["state"] != "OPEN" && td["state"] != "PENDING")) { let response = await order( accounts[account]["ACCT"], accounts[account]["APIKEY"], req.params.instrument, req.params.quantity ); console.log(response["data"]); if (typeof response["data"]["orderFillTransaction"] !== "undefined") { let delta = 0.0012; let askGap = ASKGAP; let bidGap = BIDGAP; let ntp = NTP; if (req.params.instrument.includes("JPY")) { delta = 0.12; askGap = ASKGAP * 100; bidGap = BIDGAP * 100; ntp = ntp * 100; } if (typeof(STATE[account]) === 'undefined') { STATE[account] = {}; } if (typeof(STATE[account][req.params.instrument]) === 'undefined') { STATE[account][req.params.instrument] = {}; } STATE[account][req.params.instrument]["TP"] = false; STATE[account][req.params.instrument]["qty"] = req.params.quantity; STATE[account][req.params.instrument]["trade_id"] = response["data"]["orderFillTransaction"]["tradeOpened"]["tradeID"]; if (req.params.quantity > 0) { STATE[account][req.params.instrument]["TPVal"] = askGap; STATE[account][req.params.instrument]["NTPVal"] = ntp; STATE[account][req.params.instrument]["base"] = Number(response["data"]["orderFillTransaction"]["price"]) + delta; STATE[account][req.params.instrument]["watch"] = "ask"; STATE[account][req.params.instrument]["trigger"] = (Number(response["data"]["orderFillTransaction"]["price"]) + delta) * askGap; } else { STATE[account][req.params.instrument]["NTPVal"] = ntp; STATE[account][req.params.instrument]["TPVal"] = bidGap; STATE[account][req.params.instrument]["base"] = Number(response["data"]["orderFillTransaction"]["price"]) - delta; STATE[account][req.params.instrument]["watch"] = "bid"; STATE[account][req.params.instrument]["trigger"] = (Number(response["data"]["orderFillTransaction"]["price"]) + delta) * bidGap; } } } } res.header("Access-Control-Allow-Origin", "*"); res.json("ok"); }); app.set('json spaces', 2) app.listen(port, host, () => { console.log(`osapi started`); loadState(); }); async function price_stream() { const response = await fetch( `${STREAMURL_DEMO}/accounts/101-001-8005237-001/pricing/stream?instruments=GBP_CAD%2CNZD_CAD%2CEUR_CHF%2CEUR_CAD%2CNZD_CHF%2CCHF_JPY%2CUSD_CHF%2CAUD_JPY%2CEUR_USD%2CNZD_USD%2CUSD_JPY%2CGBP_AUD%2CEUR_AUD%2CCAD_JPY%2CEUR_GBP%2CAUD_CAD%2CEUR_JPY%2CAUD_CHF%2CCAD_CHF%2CGBP_JPY%2CUSD_CAD%2CNZD_JPY%2CUSD_SGD%2CAUD_USD%2CGBP_CHF%2CAUD_NZD%2CGBP_USD`, { method: "GET", headers: { Authorization: "Bearer e88218d201bd344c2dc3c469f8f8d1f3-e77504680a17f51f0baecf9dababa40b", }, } ); try { for await (const chunk of response.body) { try { const x = JSON.parse(chunk.toString()); if (x["type"] == "PRICE") { let delta = x["asks"][0]["price"] - x["bids"][0]["price"]; client.query( `insert into pricing (instrument, price_data, tick, bid,ask,spread) values ($1,$2,$3,$4,$5,$6)`, [ x["instrument"], x, x["time"], x["asks"][0]["price"], x["bids"][0]["price"], delta.toFixed(6), ] ); PRICES[x["instrument"]] = { ask: Number(x["asks"][0]["price"]), bid: Number(x["bids"][0]["price"]), }; } } catch (e) { } } } catch (err) { console.log("pricing error"); console.error(err); console.error(err.stack); } return await price_stream(); } loadState() await price_stream();